One2Pays

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

PropertyTypeDescription
codestringA short string identifying the error
messagestringA human-readable message describing the error
metadataobjectOptional 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

CodeDescription
INVALID_CREDENTIALSInvalid API key, signature, or expired timestamp
UNAUTHORIZEDMissing required authentication headers
FORBIDDENIntegration is not active or environment mismatch

Validation Errors

CodeDescription
VALIDATION_ERRORInvalid request parameters
INVALID_AMOUNTAmount must be a valid decimal number
INVALID_CURRENCYCurrency must be 'THB'
INVALID_REFERENCE_IDReference ID format is invalid
INVALID_BANK_CODEBank code must be a valid 3-digit Thai bank code
MISSING_REQUIRED_FIELDA required field is missing

Resource Errors

CodeDescription
RESOURCE_NOT_FOUNDThe requested resource was not found
DUPLICATE_REFERENCE_IDReference ID already exists for this merchant
INSUFFICIENT_BALANCEMerchant account has insufficient balance

Payment Errors

CodeDescription
PAYMENT_NOT_CANCELLABLEPayment cannot be cancelled in its current state
PAYMENT_NOT_CAPTURABLEPayment cannot be captured in its current state
PAYMENT_NOT_REFUNDABLEPayment cannot be refunded in its current state

Rate Limiting Errors

CodeDescription
RATE_LIMIT_EXCEEDEDToo many requests in a short period

Server Errors

CodeDescription
INTERNAL_ERRORAn internal server error occurred
SERVICE_UNAVAILABLEThe service is temporarily unavailable
MAINTENANCE_MODEMerchant 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;
    }
  }
}

On this page