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:
Authentication Issues
Issue: “401 Unauthorized” Response
Symptoms:
{
"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:
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
// 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
// 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
Required 3DS Form Fields:
<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_urlnot 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:
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
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:
- Check purchase status — must be “authorized”
- Verify capture amount ≤ authorized amount
- Check authorization age (7-30 days typical expiry)
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
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)Preauthorisation Issues
Issue: Preauthorisation Not Permitted
Error:zero_auth_not_permitted
Solutions:
- Contact Precium to enable preauthorisation
- Use a minimum amount (e.g., R1.00) and immediately refund
- Use network token validation instead
Issue: Preauthorisation Doesn’t Generate Token
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 |
# 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
# Request fresh cryptogram immediately before transaction
cryptogram = request_new_cryptogram_from_token_service()
# Submit transaction immediately afterWebhook Issues
Issue: Webhooks Not Arriving
Verification:
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:
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
# Callbacks are signed with asymmetric RSA, not a shared secret.
# X-Signature = base64( RSA PKCS#1 v1.5 over the SHA-256 digest of the RAW body )
# Webhook subscriptions: public_key on the webhook object.
# Per-purchase success_callback: GET /public_key/
print(f"Received X-Signature: {signature}")
print(f"Raw payload length: {len(raw_body)}")
public_key = serialization.load_pem_public_key(public_key_pem.encode())
try:
public_key.verify(
base64.b64decode(signature),
raw_body, # raw bytes, never a re-serialised dict
padding.PKCS1v15(),
hashes.SHA256(),
)
print("Signature valid")
except Exception as exc:
print(f"Signature INVALID: {exc}")Issue: Webhook Timeout Errors
Solution — Async Processing:
# 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 immediatelyIssue: Duplicate Webhooks
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)
raiseIssue: Testing Webhooks Locally
# Install and start ngrok
ngrok http 3000
# Use the generated URL in Precium dashboard
# e.g., https://abc123.ngrok.io/webhooks/preciumTrigger a test webhook:
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:
- Suggest the customer contact their bank
- Offer an alternative payment method
- 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_idmatches original CIToriginal_amount_centsmatches tokenization amountis_recurring: truein purchase
Issue: Refund Amount Exceeds Original
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) | Cannot be voided — the refund runs to completion |
| 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:
- Use connection pooling
- Implement request timeouts
- Cache client IDs (don’t create new clients each time)
- Use webhooks instead of polling for status
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 |