Troubleshooting
Solutions for common integration issues
Troubleshooting
Common issues and how to fix them.
#3D Secure Always Fails
The most common cause is a missing or incomplete device object. All fields below are required for 3D Secure authentication to succeed.
Collect these values client-side from the browser:
const device = {
ip: "collect-server-side",
userAgent: navigator.userAgent,
accept: "text/html,application/xhtml+xml",
acceptLanguage: navigator.language,
javaEnabled: navigator.javaEnabled(),
javaScriptEnabled: true,
deviceLanguage: navigator.language.split('-')[0],
colorDepth: String(screen.colorDepth),
screenHeight: String(screen.height),
screenWidth: String(screen.width),
deviceTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone
};Important: The
ipfield must be collected server-side from the incoming request headers (e.g.,X-Forwarded-For). Do not attempt to collect it in the browser.
#Webhook Callback Not Arriving
Checklist:
- Callback URL must be publicly accessible (not
localhostor a private IP). - Your endpoint must return HTTP
200within 10 seconds. Slow responses are treated as failures. - Check firewall and security group rules — Exirom's servers must be able to reach your URL.
Retry behavior: Exirom retries failed callbacks 5 times with exponential backoff: 2, 4, 8, 16, 32 minutes. After all retries (~62 minutes total), the callback is abandoned.
Fallback: If callbacks are unreliable, poll the status endpoint instead:
- Cards:
GET /api/v1/payments/card/status/{paymentId} - APMs:
GET /api/v1/payments/apm/info/{paymentId}
See Callback Retry Policy for full details. For production-grade webhook handling patterns, see Webhook Best Practices.
#APM Checksum Mismatch
Common causes:
- Type mismatch — The amount is sent as a string in the request (
"200.00") but returned as a number in the callback (200.0). When verifying callback checksums, use the callback amount value (numeric), not the original request amount (string). - Field order — Checksum fields must be concatenated in the exact order specified in the documentation. Any deviation produces a different hash.
See Checksum Guide for the full field order and code examples in JavaScript, Python, Go, and Kotlin.
#Transaction Stuck in PENDING
A transaction in PENDING status is usually waiting for customer action (3DS challenge, redirect completion, QR scan).
- Poll the status endpoint to check for updates.
- If the transaction has been
PENDINGfor more than 30 minutes with no customer action, it will eventually time out toFAILED. - Do not create a new transaction for the same order. Use the same
requestIdto ensure idempotency. Duplicate transactions for the same order can result in double charges.
For detailed decision trees on handling stuck, failed, and ambiguous transactions, see Error Recovery Patterns.
#Authentication Token Expired
Tokens are valid for 30 days. Cache the token and reuse it across requests. Calling auth before every request is a common mistake — see Authenticating Per Request for the full explanation.
Recommended recovery pattern when a 401 is received:
- Send the API request with the cached token.
- If you receive a
401 Unauthorizedresponse, callPOST /api/v1/authto get a new token. - Retry the original request once with the new token.
Do not authenticate before every API call — this adds unnecessary latency.
See Authentication Guide for full details and Going to Production for token caching patterns.
#CORS Errors in API Playground
All Exirom API calls are server-to-server. Calling the API from client-side JavaScript will fail due to CORS restrictions.
If you hit CORS errors in the API Playground:
- Use the Copy as cURL button and run the request from your terminal.
- In production, always proxy API calls through your backend. Never call the Exirom API directly from the browser.
#See Also
- Error Recovery Patterns -- decision trees for failed and ambiguous transactions
- Common Integration Mistakes -- avoid frequent pitfalls