This guide helps you diagnose and resolve common issues when integrating with the Precium S2S API.
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]
Symptoms:
JSON
{
"error": "Invalid or missing API key"
}
Solutions:
| Cause | Solution |
|---|---|
| Missing Authorization header | Add Authorization: Bearer <api_key> 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"
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)
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>
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.
| 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"
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]
| 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:
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
| 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
}
}
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
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}'
| 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)}")
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}")
| Scenario | Timeline |
|---|---|
| Same-day (pre-settlement) | Void — immediate |
| Post-settlement | 5-10 business days |
| International cards | Up to 15 business days |
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:
| 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 |