This guide helps you diagnose and resolve common issues when integrating with the Precium S2S API.
Use this flowchart to identify your issue category:
Symptoms:
JSON
{
"error": "Invalid or missing API key"
}
Solutions:
Verification:
BASH
curl -X GET https://gate.reviopay.com/api/v1/clients/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
Common Causes:
Solution: Contact Precium support to verify your account permissions.
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)
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>
Causes:
callback_url not accessible from internet
Solution: Ensure your callback URL is publicly accessible, uses HTTPS in production, and returns HTTP 200 within 5 seconds.
3ds_authentication_failed ErrorAllow the customer to retry. If persistent with a specific card/bank, investigate with Precium support.
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.
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"
Diagnostic Steps:
PYTHON
purchase = client.get_purchase(purchase_id)
if purchase['status'] != 'authorized':
print(f"Cannot capture - status is: {purchase['status']}")
Error: partial_capture_not_supported
Solution:
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)
Error: zero_auth_not_permitted
Solutions:
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
)
Error: invalid_external_3ds_data
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
}
}
Error: external_3ds_not_enabled
Cause: Must use a Non-3DS brand for external MPI. Contact Precium to create a Non-3DS brand.
Error: invalid_network_token
Request fresh token from MDES/VTS and verify token requestor ID is correct.
Error: cryptogram_expired
PYTHON
# Request fresh cryptogram immediately before transaction
cryptogram = request_new_cryptogram_from_token_service()
# Submit transaction immediately after
Verification:
BASH
curl -X POST https://your-webhook-url.com/precium \
-H "Content-Type: application/json" \
-d '{"test": true}'
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)}")
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
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
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"
}'
Customer Message: "Your payment couldn't be processed due to insufficient funds. Please add funds to your account and try again."
Potential Causes:
Actions:
Common Causes:
previous_network_transaction_idoriginal_amount_cents
Verification Checklist:
previous_network_transaction_id matches original CIToriginal_amount_cents matches tokenization amountis_recurring: true in purchase
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}")
Test card numbers only work in the sandbox environment. Use real cards in production.
Sandbox doesn't process real cards. Use designated test card numbers for sandbox testing.
Optimisation Checklist:
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)
Contact Precium support (support@precium.com) when:
Include in Support Request: