Error Handling
Understand Omise API errors and implement robust error handling for reliable payment integrations. Learn about HTTP status codes, error response formats, and best practices for debugging.
Overviewโ
The Omise API uses conventional HTTP response codes to indicate the success or failure of an API request. Errors return JSON-encoded responses with detailed information to help you debug and handle failures gracefully.
- 2xx codes โ Success
- 4xx codes โ Client errors (your request was invalid)
- 5xx codes โ Server errors (something went wrong on Omise's side)
- All errors return JSON with
object: "error"and descriptive fields
Error Response Formatโ
All API errors return a consistent JSON structure:
{
"object": "error",
"location": "https://www.omise.co/api-errors#authentication-failure",
"code": "authentication_failure",
"message": "authentication failed"
}
Error Object Fieldsโ
| Field | Type | Description |
|---|---|---|
object | string | Always "error" for error responses |
location | string | URL to documentation for this error type |
code | string | Machine-readable error code (lowercase with underscores) |
message | string | Human-readable error message in English |
Some errors may include additional context-specific fields like charge_id, customer_id, or validation_errors for detailed debugging.
HTTP Status Codesโ
2xx Successโ
200 OKโ
Meaning: Request succeeded
{
"object": "charge",
"id": "chrg_test_5xuy4w91xqz7d1w9u0t",
"amount": 100000,
"status": "successful"
}
When it occurs:
- โ GET requests returned data successfully
- โ POST/PATCH requests completed successfully
- โ DELETE requests removed resource successfully
201 Createdโ
Meaning: Resource created successfully
When it occurs:
- โ POST request created a new resource (rare, usually returns 200)
4xx Client Errorsโ
Client errors indicate problems with the request you sent. Fix the request and retry.
400 Bad Requestโ
Meaning: The request was malformed or contains invalid parameters
{
"object": "error",
"location": "https://www.omise.co/api-errors#bad-request",
"code": "bad_request",
"message": "amount must be at least 2000 (in subunits)"
}
Common causes:
- โ Missing required parameters
- โ Invalid parameter values
- โ Amount below minimum threshold
- โ Unsupported currency
- โ Malformed JSON payload
- โ Invalid parameter types
Example scenarios:
# Missing required parameter
curl https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-d "currency=thb"
# Error: amount is required
# Amount too small
curl https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-d "amount=100" \
-d "currency=thb"
# Error: amount must be at least 2000 (20 THB)
How to fix:
- โ Check API documentation for required parameters
- โ Validate parameter values before sending
- โ Ensure proper data types (numbers as numbers, not strings)
- โ Verify currency-specific minimum amounts
401 Unauthorizedโ
Meaning: Authentication failed - invalid or missing API key
{
"object": "error",
"location": "https://www.omise.co/api-errors#authentication-failure",
"code": "authentication_failure",
"message": "authentication failed"
}
Common causes:
- โ No API key provided
- โ Invalid API key format
- โ Revoked or expired API key
- โ Wrong key for environment (test key in production)
- โ API key not properly encoded in Authorization header
Example scenarios:
# Missing API key
curl https://api.omise.co/account
# Error: authentication failed
# Invalid API key
curl https://api.omise.co/account \
-u invalid_key:
# Error: authentication failed
# Using public key for secret key operation
curl https://api.omise.co/charges \
-X POST \
-u pkey_test_...: \
-d "amount=100000" \
-d "currency=thb"
# Error: authentication failed
How to fix:
- โ Verify API key is correct (copy from Dashboard)
- โ Check for extra spaces or characters
- โ Use secret key for server-side operations
- โ Use public key only for tokens/sources
- โ Ensure key hasn't been revoked
- โ Verify test vs live key matches environment
Learn more about Authentication โ
402 Payment Requiredโ
Meaning: Card or payment method was declined
{
"object": "error",
"location": "https://www.omise.co/api-errors#payment-declined",
"code": "payment_declined",
"message": "Your card was declined",
"charge_id": "chrg_test_5xuy4w91xqz7d1w9u0t"
}
Common causes:
- โ Insufficient funds
- โ Card expired
- โ Incorrect card details (CVV, expiry)
- โ Card issuer declined (fraud suspicion)
- โ Card not enabled for online payments
- โ Transaction limit exceeded
Payment-specific error codes:
| Code | Meaning | Action |
|---|---|---|
insufficient_fund | Not enough money on card | Ask customer to use different card |
invalid_card | Card number invalid | Verify card number |
stolen_or_lost_card | Card reported stolen/lost | Ask customer to contact bank |
failed_processing | Processing failed | Retry or use different card |
payment_cancelled | Customer cancelled payment | Customer action, no fix needed |
payment_expired | Payment window expired | Create new charge |
Example scenario:
# Charge attempt with insufficient funds
curl https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-d "amount=1000000" \
-d "currency=thb" \
-d "card=tokn_test_..."
# Error: insufficient_fund
How to fix:
- โ Show clear error message to customer
- โ Ask customer to try different payment method
- โ Don't retry same card multiple times (avoid card blocks)
- โ Suggest customer contacts their bank
- โ Log decline reason for analysis
Repeatedly attempting declined cards can trigger fraud alerts and get cards blocked. Instead, ask customers to verify their payment details or use an alternative payment method.
404 Not Foundโ
Meaning: The requested resource doesn't exist
{
"object": "error",
"location": "https://www.omise.co/api-errors#not-found",
"code": "not_found",
"message": "charge chrg_test_invalid was not found"
}
Common causes:
- โ Invalid resource ID
- โ Resource was deleted
- โ Wrong API endpoint URL
- โ Typo in resource ID
- โ Using test ID in live mode (or vice versa)
Example scenarios:
# Invalid charge ID
curl https://api.omise.co/charges/chrg_invalid \
-u skey_test_...:
# Error: not found
# Wrong endpoint
curl https://api.omise.co/charge/chrg_test_... \
-u skey_test_...:
# Error: not found (should be /charges not /charge)
How to fix:
- โ Verify resource ID is correct
- โ Check resource wasn't deleted
- โ Ensure test/live mode matches key
- โ Verify endpoint URL spelling
- โ Search for resource using list endpoint
422 Unprocessable Entityโ
Meaning: Request was well-formed but contains semantic errors
{
"object": "error",
"location": "https://www.omise.co/api-errors#invalid-charge",
"code": "invalid_charge",
"message": "charge has already been captured"
}
Common causes:
- โ Attempting invalid state transition
- โ Business logic violation
- โ Resource already processed
- โ Invalid parameter combination
Example scenarios:
# Capturing already-captured charge
curl https://api.omise.co/charges/chrg_test_.../capture \
-X POST \
-u skey_test_...:
# Error: charge has already been captured
# Refunding more than charge amount
curl https://api.omise.co/charges/chrg_test_.../refunds \
-X POST \
-u skey_test_...: \
-d "amount=200000"
# Error: refund amount exceeds available amount
How to fix:
- โ Check resource state before operations
- โ Verify business logic constraints
- โ Don't retry same operation (it may have succeeded)
- โ Retrieve resource to check current state
429 Too Many Requestsโ
Meaning: You've exceeded rate limits
{
"object": "error",
"location": "https://www.omise.co/api-errors#rate-limit-exceeded",
"code": "rate_limit_exceeded",
"message": "too many requests, please try again later"
}
Response headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1612137600
Retry-After: 60
How to fix:
- โ Implement exponential backoff
- โ
Check
Retry-Afterheader - โ Reduce request frequency
- โ Batch operations where possible
- โ Cache responses when appropriate
Learn more about Rate Limiting โ
5xx Server Errorsโ
Server errors indicate problems on Omise's side. These are rare but should be handled gracefully.
500 Internal Server Errorโ
Meaning: Something went wrong on Omise's servers
{
"object": "error",
"location": "https://www.omise.co/api-errors#internal-server-error",
"code": "internal_server_error",
"message": "An internal server error occurred"
}
When it occurs:
- โ ๏ธ Unexpected server-side error
- โ ๏ธ Temporary service disruption
- โ ๏ธ Database connectivity issues
How to handle:
- โ Retry request with exponential backoff
- โ Check status.omise.co for incidents
- โ Contact support if persists
- โ Log error details for debugging
503 Service Unavailableโ
Meaning: Service is temporarily unavailable
{
"object": "error",
"location": "https://www.omise.co/api-errors#service-unavailable",
"code": "service_unavailable",
"message": "Service temporarily unavailable"
}
When it occurs:
- โ ๏ธ Scheduled maintenance
- โ ๏ธ Service overload
- โ ๏ธ Upstream provider issues
How to handle:
- โ
Retry after delay (check
Retry-Afterheader) - โ Show maintenance message to users
- โ Queue requests for later processing
- โ Check status page
Common Error Codesโ
Authentication Errorsโ
| Code | HTTP Status | Description | Solution |
|---|---|---|---|
authentication_failure | 401 | Invalid or missing API key | Verify API key is correct |
forbidden | 403 | Key lacks required permissions | Use secret key for this operation |
Request Errorsโ
| Code | HTTP Status | Description | Solution |
|---|---|---|---|
bad_request | 400 | Invalid request parameters | Check required parameters and formats |
invalid_charge | 422 | Charge state doesn't allow operation | Verify charge status first |
invalid_customer | 422 | Customer operation not allowed | Check customer state |
not_found | 404 | Resource doesn't exist | Verify resource ID |
Payment Errorsโ
| Code | HTTP Status | Description | Solution |
|---|---|---|---|
payment_declined | 402 | Payment was declined | Ask customer to use different card |
insufficient_fund | 402 | Not enough balance | Request different payment method |
invalid_card | 402 | Card details invalid | Verify card number and CVV |
stolen_or_lost_card | 402 | Card reported stolen/lost | Customer must contact bank |
failed_processing | 402 | Payment processing failed | Retry or use different card |
payment_cancelled | 402 | Customer cancelled payment | Customer action |
payment_expired | 402 | Payment window expired | Create new charge |
System Errorsโ
| Code | HTTP Status | Description | Solution |
|---|---|---|---|
internal_server_error | 500 | Server-side error | Retry with backoff |
service_unavailable | 503 | Service temporarily down | Wait and retry |
rate_limit_exceeded | 429 | Too many requests | Implement rate limiting |
Error Handling Best Practicesโ
1. Always Check HTTP Status Firstโ
# โ
Good - Check status code
require 'omise'
begin
charge = Omise::Charge.create({
amount: 100000,
currency: 'thb',
card: params[:token]
})
# Success
render json: charge
rescue Omise::Error => e
# Handle error based on type
case e.http_status
when 400
render json: { error: 'Invalid request' }, status: 400
when 401
render json: { error: 'Authentication failed' }, status: 401
when 402
render json: { error: 'Payment declined', code: e.code }, status: 402
when 404
render json: { error: 'Resource not found' }, status: 404
when 422
render json: { error: 'Invalid operation', message: e.message }, status: 422
when 429
render json: { error: 'Rate limit exceeded' }, status: 429
when 500, 503
render json: { error: 'Service temporarily unavailable' }, status: 503
else
render json: { error: 'An error occurred' }, status: 500
end
end
2. Parse Error Detailsโ
# โ
Good - Extract error details
import omise
try:
charge = omise.Charge.create(
amount=100000,
currency='thb',
card=token
)
return charge
except omise.errors.BaseError as e:
error_code = e.code
error_message = e.message
# Log for debugging
logger.error(f"Charge failed: {error_code} - {error_message}")
# Return user-friendly message
if error_code == 'insufficient_fund':
return {'error': 'Insufficient funds. Please try a different card.'}
elif error_code == 'invalid_card':
return {'error': 'Invalid card details. Please check and try again.'}
else:
return {'error': 'Payment failed. Please try again.'}
3. Implement Retry Logic for Server Errorsโ
// โ
Good - Retry with exponential backoff
const omise = require('omise')({ secretKey: process.env.OMISE_SECRET_KEY });
async function createChargeWithRetry(chargeData, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const charge = await omise.charges.create(chargeData);
return { success: true, charge };
} catch (error) {
const isServerError = error.statusCode >= 500;
const isLastAttempt = attempt === maxRetries - 1;
if (isServerError && !isLastAttempt) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Don't retry client errors or last attempt
return {
success: false,
error: {
code: error.code,
message: error.message,
statusCode: error.statusCode
}
};
}
}
}
4. Validate Before Sending Requestsโ
<?php
// โ
Good - Validate before API call
function createCharge($amount, $currency, $token) {
// Validate amount
$minAmounts = [
'thb' => 2000, // 20 THB
'jpy' => 50, // 50 JPY
'sgd' => 100, // 1 SGD
];
if ($amount < $minAmounts[$currency]) {
return [
'error' => 'Amount below minimum for ' . strtoupper($currency),
'min_amount' => $minAmounts[$currency]
];
}
// Validate currency
$supportedCurrencies = ['thb', 'jpy', 'sgd', 'myr', 'usd'];
if (!in_array($currency, $supportedCurrencies)) {
return [
'error' => 'Unsupported currency',
'supported' => $supportedCurrencies
];
}
// Validate token format
if (!preg_match('/^tokn_test_[a-z0-9]+$/', $token) &&
!preg_match('/^tokn_[a-z0-9]+$/', $token)) {
return ['error' => 'Invalid token format'];
}
try {
$charge = OmiseCharge::create([
'amount' => $amount,
'currency' => $currency,
'card' => $token
]);
return ['success' => true, 'charge' => $charge];
} catch (Exception $e) {
return [
'error' => $e->getMessage(),
'code' => $e->getCode()
];
}
}
5. Show User-Friendly Messagesโ
// โ
Good - Translate error codes to user messages
package main
import (
"github.com/omise/omise-go"
)
func getUserMessage(err error) string {
if omiseErr, ok := err.(*omise.Error); ok {
switch omiseErr.Code {
case "authentication_failure":
return "A system error occurred. Please try again later."
case "insufficient_fund":
return "Your card has insufficient funds. Please use a different card."
case "invalid_card":
return "The card details are invalid. Please check and try again."
case "stolen_or_lost_card":
return "This card cannot be used. Please contact your bank."
case "payment_cancelled":
return "Payment was cancelled. You can try again when ready."
case "payment_expired":
return "Payment session expired. Please start a new payment."
case "rate_limit_exceeded":
return "Too many attempts. Please wait a moment and try again."
case "service_unavailable":
return "Payment service is temporarily unavailable. Please try again later."
default:
return "Payment failed. Please try again or use a different payment method."
}
}
return "An unexpected error occurred. Please try again."
}
func createCharge(amount int64, currency string, token string) (string, error) {
client, _ := omise.NewClient(
os.Getenv("OMISE_PUBLIC_KEY"),
os.Getenv("OMISE_SECRET_KEY"),
)
charge, err := client.CreateCharge(&omise.ChargeParams{
Amount: amount,
Currency: currency,
Card: token,
})
if err != nil {
userMessage := getUserMessage(err)
return userMessage, err
}
return "Payment successful!", nil
}
6. Log Errors for Debuggingโ
# โ
Good - Comprehensive error logging
require 'logger'
logger = Logger.new('omise_errors.log')
begin
charge = Omise::Charge.create({
amount: 100000,
currency: 'thb',
card: token
})
rescue Omise::Error => e
# Log detailed error information
logger.error({
timestamp: Time.now.iso8601,
error_type: 'omise_api_error',
http_status: e.http_status,
error_code: e.code,
error_message: e.message,
request_id: e.request_id,
charge_params: {
amount: 100000,
currency: 'thb',
token: token[0..10] + '...' # Partial token for security
},
backtrace: e.backtrace.first(5)
}.to_json)
# Re-raise or handle
raise
end
7. Don't Expose Sensitive Error Detailsโ
// โ Bad - Exposes internal details
app.post('/charge', async (req, res) => {
try {
const charge = await omise.charges.create(req.body);
res.json(charge);
} catch (error) {
// DON'T DO THIS - Exposes API key, internal paths, etc.
res.status(500).json({ error: error.toString() });
}
});
// โ
Good - Safe error messages
app.post('/charge', async (req, res) => {
try {
const charge = await omise.charges.create({
amount: req.body.amount,
currency: req.body.currency,
card: req.body.token
});
res.json({ success: true, charge_id: charge.id });
} catch (error) {
// Log full error server-side
console.error('Charge failed:', error);
// Return safe message to client
const safeMessage = {
success: false,
error: {
code: error.code || 'unknown',
message: getUserFriendlyMessage(error.code)
}
};
res.status(error.statusCode || 500).json(safeMessage);
}
});
Debugging Errorsโ
1. Use Test Mode for Developmentโ
Always use test API keys during development:
# โ
Test mode - safe for debugging
export OMISE_SECRET_KEY=skey_test_5xuy4w91xqz7d1w9u0t
# Test mode errors are identical to live mode
curl https://api.omise.co/charges \
-X POST \
-u $OMISE_SECRET_KEY: \
-d "amount=100000" \
-d "currency=thb" \
-d "card=tokn_test_no1t4tnemucod0e51mo"
2. Check Request/Response in Dashboardโ
- Go to Dashboard โ Logs
- View all API requests and responses
- See exact error details
- Review request parameters
3. Enable Debug Loggingโ
# Ruby - Enable debug logging
require 'omise'
Omise.api_key = ENV['OMISE_SECRET_KEY']
Omise.debug = true # Prints HTTP requests/responses
charge = Omise::Charge.create({
amount: 100000,
currency: 'thb',
card: token
})
# Python - Enable debug logging
import omise
import logging
# Enable HTTP debugging
logging.basicConfig(level=logging.DEBUG)
omise.api_secret = os.environ['OMISE_SECRET_KEY']
// Node.js - Use request interception
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
// Log all requests/responses
omise.interceptor = {
request: (config) => {
console.log('Request:', config);
return config;
},
response: (response) => {
console.log('Response:', response);
return response;
},
responseError: (error) => {
console.error('Error:', error);
throw error;
}
};
4. Test with Curlโ
# Verbose output shows full HTTP exchange
curl -v https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-d "amount=100000" \
-d "currency=thb" \
-d "card=tokn_test_..."
# Shows:
# > POST /charges HTTP/1.1
# > Authorization: Basic ...
# > Content-Type: application/x-www-form-urlencoded
# < HTTP/1.1 200 OK
# < Content-Type: application/json
5. Common Debugging Stepsโ
For Authentication Errors:
- โ Copy API key from Dashboard (don't type manually)
- โ Check for extra spaces or line breaks
- โ Verify test vs live key matches environment
- โ
Ensure Authorization header format:
Basic base64(key:)
For Payment Errors:
- โ Try with test card numbers first
- โ Verify card details are correct
- โ Check if card supports online payments
- โ Test with different cards to isolate issue
For Validation Errors:
- โ Read error message carefully
- โ Check API documentation for parameter requirements
- โ Verify data types (number vs string)
- โ Check minimum/maximum values
For Server Errors:
- โ Check status.omise.co
- โ Retry after a delay
- โ Contact support if persistent
- โ Save request/response for debugging
Error Handling Checklistโ
Before going live, ensure your error handling:
- Catches all API exceptions/errors
- Checks HTTP status codes
- Parses error codes for specific handling
- Shows user-friendly error messages
- Logs errors with full context (server-side only)
- Implements retry logic for server errors
- Uses exponential backoff for retries
- Doesn't retry payment failures
- Validates requests before sending
- Doesn't expose sensitive error details to users
- Handles network timeouts
- Has fallback for service unavailability
- Monitors error rates
- Alerts on unusual error patterns
- Tests error scenarios in development
Testing Error Scenariosโ
Test Authentication Errorsโ
# Invalid API key
curl https://api.omise.co/account \
-u invalid_key:
# Returns 401 authentication_failure
# Wrong key type
curl https://api.omise.co/charges \
-X POST \
-u pkey_test_...: \
-d "amount=100000"
# Returns 401 authentication_failure
Test Validation Errorsโ
# Amount too small
curl https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-d "amount=100" \
-d "currency=thb"
# Returns 400 bad_request
# Invalid currency
curl https://api.omise.co/charges \
-X POST \
-u skey_test_...: \
-d "amount=100000" \
-d "currency=invalid"
# Returns 400 bad_request
Test Payment Errorsโ
Use special test cards that trigger specific errors:
# Insufficient funds
curl https://vault.omise.co/tokens \
-X POST \
-u pkey_test_...: \
-d "card[number]=4111111111111111" \
-d "card[name]=Test User" \
-d "card[expiration_month]=12" \
-d "card[expiration_year]=2025" \
-d "card[security_code]=123"
# Use token in charge - will decline with insufficient_fund
# Failed processing
curl https://vault.omise.co/tokens \
-X POST \
-u pkey_test_...: \
-d "card[number]=4000000000000002" \
-d "card[name]=Test User" \
-d "card[expiration_month]=12" \
-d "card[expiration_year]=2025" \
-d "card[security_code]=123"
# Use token in charge - will decline with failed_processing
FAQโ
Should I retry failed payments automatically?
No, don't automatically retry payment failures (4xx errors). Repeatedly attempting declined cards can:
- Trigger fraud alerts
- Get cards blocked by issuers
- Frustrate customers
Instead:
- Show clear error message
- Ask customer to verify payment details
- Suggest alternative payment method
- Let customer manually retry when ready
You can retry server errors (5xx) with exponential backoff.
How do I know if a charge failed?
Check the HTTP status code and charge status:
Success:
- HTTP 200 OK
charge.status = "successful"(captured)charge.status = "pending"(authorized, not captured yet)
Failure:
- HTTP 402 Payment Required
- Error response with code like
payment_declined charge.status = "failed"or"expired"
What's the difference between 4xx and 5xx errors?
4xx = Client Errors (your fault)
- Your request was invalid
- Fix the request and retry
- Examples: bad parameters, declined payment, invalid auth
5xx = Server Errors (our fault)
- Something went wrong on Omise's side
- Request was valid, but couldn't be processed
- Retry with backoff
- Examples: service down, internal error
How long should I wait before retrying?
Use exponential backoff:
- First retry: Wait 1 second
- Second retry: Wait 2 seconds
- Third retry: Wait 4 seconds
- Fourth retry: Wait 8 seconds
- Give up after 3-5 attempts
async function retryWithBackoff(fn, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (error.statusCode < 500 || attempt === maxAttempts - 1) {
throw error;
}
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Can I get more details about payment failures?
Payment failure reasons are intentionally limited for security. Card issuers don't always provide specific decline reasons to prevent fraud.
What you get:
- โ
High-level error code (
insufficient_fund,invalid_card) - โ Generic error message
What you don't get:
- โ Exact card balance
- โ Specific fraud flags
- โ Internal bank codes
This protects cardholders from attackers testing stolen cards.
Quick Referenceโ
Error Response Structureโ
{
"object": "error",
"location": "https://www.omise.co/api-errors#<error-code>",
"code": "<error_code>",
"message": "<human-readable message>"
}
Common Status Codesโ
| Status | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 400 | Bad Request | Fix parameters |
| 401 | Unauthorized | Fix API key |
| 402 | Payment Failed | Show error, don't retry |
| 404 | Not Found | Verify resource ID |
| 422 | Invalid State | Check resource state |
| 429 | Rate Limited | Implement backoff |
| 500 | Server Error | Retry with backoff |
| 503 | Service Down | Retry later |
Error Handling Patternโ
1. Try API request
2. Catch error
3. Check HTTP status
4. Parse error code
5. Log error (server-side)
6. Show user message (safe)
7. Retry only if 5xx
8. Monitor error rates
Related Resourcesโ
Next: Learn about Pagination to handle large result sets efficiently.