Error Handling
One2Pays uses conventional HTTP response codes to indicate the success or failure of an API request. In general:
- 2xx codes indicate success
- 4xx codes indicate an error that failed given the information provided
- 5xx codes indicate an error with One2Pays's servers
Error Response Format
All errors return a JSON object with the following structure:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Amount must be a valid decimal number",
"metadata": {
"field": "amount"
}
}
}Error Object Properties
| Property | Type | Description |
|---|---|---|
code | string | A short string identifying the error |
message | string | A human-readable message describing the error |
metadata | object | Optional additional error details (field names, etc.) |
HTTP Status Codes
200 - OK
Everything worked as expected.
201 - Created
Resource was created successfully.
400 - Bad Request
The request was unacceptable, often due to missing a required parameter or invalid data format.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Amount must be a valid decimal number",
"metadata": {
"field": "amount"
}
}
}401 - Unauthorized
No valid authentication credentials provided, or authentication failed.
{
"success": false,
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Invalid signature or expired timestamp"
}
}403 - Forbidden
Valid credentials but insufficient permissions, or integration is not active.
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Integration is not active"
}
}404 - Not Found
The requested resource doesn't exist.
{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Payment not found"
}
}409 - Conflict
The request conflicts with another request (perhaps due to using the same idempotency key or duplicate reference ID).
{
"success": false,
"error": {
"code": "DUPLICATE_REFERENCE_ID",
"message": "Reference ID already exists for this merchant"
}
}429 - Too Many Requests
Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests, please try again later",
"metadata": {
"limit": 100,
"remaining": 0,
"resetAt": "2024-01-01T00:01:00.000Z",
"retryAfter": 60
}
}
}503 - Maintenance Mode
The merchant API is temporarily paused by One2Pays. Authenticated merchant HMAC requests, including payment/deposit creation, withdraw creation, wallet, and other signed API calls, return this response while maintenance mode is active.
{
"success": false,
"error": {
"code": "MAINTENANCE_MODE",
"message": "Merchant API is currently under maintenance"
}
}When you receive MAINTENANCE_MODE, show a clear user-facing message such as "Payment service is temporarily under maintenance. Please try again later." Do not display raw technical errors to end users, and do not retry aggressively.
500, 502, 503, 504 - Server Errors
Something went wrong on One2Pays's end. (These are rare.)
{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred"
}
}Error Codes
Authentication Errors
| Code | Description |
|---|---|
INVALID_CREDENTIALS | Invalid API key, signature, or expired timestamp |
UNAUTHORIZED | Missing required authentication headers |
FORBIDDEN | Integration is not active or environment mismatch |
Validation Errors
| Code | Description |
|---|---|
VALIDATION_ERROR | Invalid request parameters |
INVALID_AMOUNT | Amount must be a valid decimal number |
INVALID_CURRENCY | Currency must be 'THB' |
INVALID_REFERENCE_ID | Reference ID format is invalid |
INVALID_BANK_CODE | Bank code must be a valid 3-digit Thai bank code |
MISSING_REQUIRED_FIELD | A required field is missing |
Resource Errors
| Code | Description |
|---|---|
RESOURCE_NOT_FOUND | The requested resource was not found |
DUPLICATE_REFERENCE_ID | Reference ID already exists for this merchant |
INSUFFICIENT_BALANCE | Merchant account has insufficient balance |
Payment Errors
| Code | Description |
|---|---|
PAYMENT_NOT_CANCELLABLE | Payment cannot be cancelled in its current state |
PAYMENT_NOT_CAPTURABLE | Payment cannot be captured in its current state |
PAYMENT_NOT_REFUNDABLE | Payment cannot be refunded in its current state |
Rate Limiting Errors
| Code | Description |
|---|---|
RATE_LIMIT_EXCEEDED | Too many requests in a short period |
Server Errors
| Code | Description |
|---|---|
INTERNAL_ERROR | An internal server error occurred |
SERVICE_UNAVAILABLE | The service is temporarily unavailable |
MAINTENANCE_MODE | Merchant API is temporarily paused for maintenance |
Error Handling Best Practices
Tip
Always handle errors gracefully in your application and provide meaningful feedback to your users.
Example Error Handling
async function createPayment(paymentData: object) {
try {
const response = await fetch('https://api.example.com/api/v1/payments', {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json',
},
body: JSON.stringify(paymentData),
});
const result = await response.json();
if (!result.success) {
// Handle specific error codes
switch (result.error.code) {
case 'VALIDATION_ERROR':
console.error('Validation error:', result.error.message);
// Show user-friendly validation message
break;
case 'INVALID_CREDENTIALS':
console.error('Authentication failed');
// Re-authenticate or show login prompt
break;
case 'MAINTENANCE_MODE':
console.error('Merchant API is under maintenance');
// Show a friendly maintenance message and pause payment/withdraw actions
break;
case 'RATE_LIMIT_EXCEEDED':
console.error('Rate limit exceeded, retrying...');
// Implement exponential backoff
const retryAfter = result.error.metadata?.retryAfter || 60;
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return createPayment(paymentData); // Retry
case 'DUPLICATE_REFERENCE_ID':
console.error('Duplicate reference ID');
// Handle duplicate (might be from retry)
break;
default:
console.error('Unexpected error:', result.error);
}
throw new Error(result.error.message);
}
return result.data;
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}Retry Logic
For RATE_LIMIT_EXCEEDED and most server errors (5xx), implement exponential backoff. Do not retry aggressively for MAINTENANCE_MODE; wait for maintenance to finish or retry with a longer polling schedule.
async function createPaymentWithRetry(paymentData, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await createPayment(paymentData);
} catch (error) {
if (error.code === 'MAINTENANCE_MODE') {
throw error; // Show maintenance UI instead of retrying immediately
}
if (error.code === 'RATE_LIMIT_EXCEEDED' && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
const retryAfter = error.metadata?.retryAfter || delay / 1000;
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
throw error;
}
}
}