Errors & Reference
Errors & Reference
HTTP Status Codes
| Code | Meaning | Description | Action |
|---|---|---|---|
| 200 | OK | Request succeeded | Continue |
| 201 | Created | Resource created | Store returned ID |
| 400 | Bad Request | Validation failed | Fix request parameters |
| 401 | Unauthorized | Missing/invalid API key | Check API key |
| 403 | Forbidden | API key revoked/expired | Regenerate API key |
| 404 | Not Found | Resource not found | Check ID |
| 409 | Conflict | Duplicate ID/reference | Use unique value |
| 429 | Too Many Requests | Rate limit exceeded | Implement backoff |
| 500 | Internal Server Error | Server error | Retry with backoff |
| 503 | Service Unavailable | Temporary outage | Retry with backoff |
Error Response Format
All errors follow this format:
{ "status": "error", "error": { "code": "ERROR_CODE", "message": "Human-readable error message" }, "metadata": { "timestamp": "2025-01-07T12:00:00Z", "api_version": "v1" }}Error Codes
General Errors
| Code | HTTP | Description |
|---|---|---|
INVALID_INPUT | 400 | Request validation failed |
VALIDATION_FAILED | 400 | Field validation failed |
UNAUTHORIZED | 401 | Authentication failed |
FORBIDDEN | 403 | Insufficient permissions |
NOT_FOUND | 404 | Resource not found |
INTERNAL_ERROR | 500 | Internal server error |
Payment Errors
| Code | HTTP | Description |
|---|---|---|
PAYMENT_NOT_FOUND | 404 | Payment not found |
INVALID_AMOUNT | 400 | Amount out of range (R55-R3000) |
INVALID_CURRENCY | 400 | Currency not supported |
DUPLICATE_ORDER_ID | 409 | merchant_order_id already used |
Payout Errors
| Code | HTTP | Description |
|---|---|---|
PAYOUT_NOT_FOUND | 404 | Payout not found |
INSUFFICIENT_BALANCE | 400 | Not enough available balance |
INVALID_PAYOUT_METHOD | 400 | Unsupported payout method |
INVALID_PAYMENT_DETAILS | 400 | Bank details validation failed |
INVALID_STATUS_TRANSITION | 400 | Invalid status change |
Idempotency
Payment Initialization
Use Idempotency-Key header for safe retries:
curl -X POST "https://api.qrpay2.com/api/v1/payments/init" \ -H "X-API-Key: $API_KEY" \ -H "Idempotency-Key: ORDER-123-1704636000-abc123" \ -H "Content-Type: application/json" \ -d '{"amount": 10000, ...}'Behavior:
- Same key + identical payload → Same
payment_id(200 OK) - Same key + different payload → Conflict (409)
Recommended format: {merchant_order_id}-{timestamp}-{random}
Payout Idempotency
Use merchant_reference for idempotent payout creation:
- Same reference + identical params → Return existing payout
- Same reference + different params → Conflict (409)
Webhook Idempotency
Use X-QRPay-Event-Id for webhook processing:
- Store event ID before processing
- Return 200 OK for duplicate events
Best Practices
Security
- Store API keys securely - Use environment variables or secrets manager
- Never log secrets - Mask API keys and webhook secrets
- Use HTTPS - Required for all production requests
- Rotate keys regularly - Coordinate with account manager
- Validate timestamps - Prevent replay attacks on webhooks
- Don’t expose details - Keep payment_details out of logs
Error Handling
- Implement exponential backoff for 5xx and 429 errors
- Log all errors with request context
- Don’t retry 4xx (except 429) without fixing the issue
- Handle insufficient balance by checking before retries
Monitoring
- Track success rates - Detect issues early
- Monitor webhook delivery - Alert on failures
- Set balance alerts - Prevent payout failures
- Log state transitions - Audit trail for debugging
Rate Limits
| Endpoint | Limit |
|---|---|
| Payment init | 100/min per merchant |
| Payment status | 300/min per merchant |
| Payout create | 50/min per merchant |
| Payout status | 300/min per merchant |
| Balance check | 60/min per merchant |
When rate limited (429), implement exponential backoff starting at 1 second.