Skip to main content
Version: 2019-05-29 (Current)

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.

Quick Reference
  • 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โ€‹

FieldTypeDescription
objectstringAlways "error" for error responses
locationstringURL to documentation for this error type
codestringMachine-readable error code (lowercase with underscores)
messagestringHuman-readable error message in English
Additional Fields

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:

  1. โœ… Check API documentation for required parameters
  2. โœ… Validate parameter values before sending
  3. โœ… Ensure proper data types (numbers as numbers, not strings)
  4. โœ… 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:

  1. โœ… Verify API key is correct (copy from Dashboard)
  2. โœ… Check for extra spaces or characters
  3. โœ… Use secret key for server-side operations
  4. โœ… Use public key only for tokens/sources
  5. โœ… Ensure key hasn't been revoked
  6. โœ… 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:

CodeMeaningAction
insufficient_fundNot enough money on cardAsk customer to use different card
invalid_cardCard number invalidVerify card number
stolen_or_lost_cardCard reported stolen/lostAsk customer to contact bank
failed_processingProcessing failedRetry or use different card
payment_cancelledCustomer cancelled paymentCustomer action, no fix needed
payment_expiredPayment window expiredCreate 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:

  1. โœ… Show clear error message to customer
  2. โœ… Ask customer to try different payment method
  3. โœ… Don't retry same card multiple times (avoid card blocks)
  4. โœ… Suggest customer contacts their bank
  5. โœ… Log decline reason for analysis
Don't Retry Payment Failures

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:

  1. โœ… Verify resource ID is correct
  2. โœ… Check resource wasn't deleted
  3. โœ… Ensure test/live mode matches key
  4. โœ… Verify endpoint URL spelling
  5. โœ… 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:

  1. โœ… Check resource state before operations
  2. โœ… Verify business logic constraints
  3. โœ… Don't retry same operation (it may have succeeded)
  4. โœ… 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:

  1. โœ… Implement exponential backoff
  2. โœ… Check Retry-After header
  3. โœ… Reduce request frequency
  4. โœ… Batch operations where possible
  5. โœ… 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:

  1. โœ… Retry request with exponential backoff
  2. โœ… Check status.omise.co for incidents
  3. โœ… Contact support if persists
  4. โœ… 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:

  1. โœ… Retry after delay (check Retry-After header)
  2. โœ… Show maintenance message to users
  3. โœ… Queue requests for later processing
  4. โœ… Check status page

Complete Error Code Referenceโ€‹

400 Bad Requestโ€‹

CodeMessageDescription
backend_error"backend error"Communication failure with payment provider
bad_request"bad request"Invalid request parameters
brand_not_supported"brand not supported"Card brand not accepted by account
documents_locked"documents list cannot be modified"Dispute no longer open for file uploads
expired_charge"charge expired"Authorization window exceeded or payment code expired
failed_capture"capture failed"Charge capture unsuccessful
failed_expire"expire failed"Unable to set charge expiration
failed_fraud_check"fraud check failed"Card flagged as fraudulent
failed_multi_currency"the currency conversion could not be completed"Multi-currency conversion error
failed_refund"refund failed"Refund processing error
failed_reverse"reverse failed"Charge reversal unsuccessful
failed_void"void failed"Charge void operation unsuccessful
feature_not_supported"feature not supported"Account lacks feature access
invalid_amount"invalid amount"Non-integer amount submitted
invalid_bank_account"invalid bank account"Bank account details invalid
invalid_card"invalid card"Card token creation failed
invalid_card_token"invalid card token"Non-string token object supplied
invalid_charge"invalid charge"Charge fails minimum requirements
invalid_dispute"invalid dispute"Dispute modification outside parameters
invalid_domain"request must be made with the vault.omise.co domain"Incorrect domain for token generation
invalid_file_type"invalid content-type"Unsupported file type uploaded
invalid_link"invalid link"Link missing currency or description
invalid_recipient"invalid recipient"Recipient invalid or nonexistent
invalid_transfer"invalid transfer"Transfer creation/update error
missing_card"request contains no card parameters"Card parameters absent in request
missing_file"missing file or filename"File upload without file content
used_token"token was already used"Single-use token reused

401 Unauthorizedโ€‹

CodeMessageDescription
authentication_failure"authentication failed"Invalid or missing API key

402 Payment Requiredโ€‹

CodeMessageDescription
insufficient_fund"insufficient funds"Not enough money on card
invalid_card"invalid card"Card number invalid
stolen_or_lost_card"stolen or lost card"Card reported stolen/lost
failed_processing"processing failed"Processing failed
payment_cancelled"payment cancelled"Customer cancelled payment
payment_expired"payment expired"Payment window expired
payment_rejected"payment rejected"Payment was rejected

403 Forbiddenโ€‹

CodeMessageDescription
key_expired_error"expired key"API key has expired
locked_account_error"account locked"Account has been locked
not_authorized"not authorized"Operation not permitted for this key

404 Not Foundโ€‹

CodeMessageDescription
not_found"the requested object was not found"Resource doesn't exist
serializer_not_found"your current API version does not support this action"API version incompatibility
service_not_found"you are using an api version that does not support this operation"Operation not supported in API version

422 Unprocessable Entityโ€‹

CodeMessageDescription
failed_deletion"this object could not be deleted"Deletion operation failed
invalid_filter"invalid filters"Invalid search/list filters
invalid_page"invalid page"Invalid pagination page number
invalid_per_page"invalid per page"Invalid items per page value
invalid_scope"invalid scope"Invalid search scope

429 Too Many Requestsโ€‹

CodeMessageDescription
too_many_requests"you have sent too many requests in too little time"Rate limit exceeded

500 Internal Server Errorโ€‹

CodeMessageDescription
internal_error"request could not be completed due to an internal error"Server-side error

503 Service Unavailableโ€‹

CodeMessageDescription
search_unavailable"search is temporarily unavailable"Search service temporarily down

Error Codes by Categoryโ€‹

Authentication Errorsโ€‹

CodeHTTP StatusDescriptionSolution
authentication_failure401Invalid or missing API keyVerify API key is correct
key_expired_error403API key has expiredGenerate new API key from Dashboard
locked_account_error403Account has been lockedContact support
not_authorized403Operation not permittedUse correct key type (public/secret)

Request Errorsโ€‹

CodeHTTP StatusDescriptionSolution
bad_request400Invalid request parametersCheck required parameters and formats
invalid_amount400Non-integer amountUse integer amount in smallest unit
invalid_card_token400Invalid token formatEnsure token is a string
invalid_domain400Wrong domain for requestUse vault.omise.co for tokens
missing_card400Missing card parametersInclude card or token in request
used_token400Token already usedTokens are single-use, create new one
invalid_charge422Charge state doesn't allow operationVerify charge status first
not_found404Resource doesn't existVerify resource ID

Payment Errorsโ€‹

CodeHTTP StatusDescriptionSolution
insufficient_fund402Not enough balanceRequest different payment method
invalid_card400/402Card details invalidVerify card number and CVV
stolen_or_lost_card402Card reported stolen/lostCustomer must contact bank
failed_processing402Payment processing failedRetry or use different card
failed_fraud_check400Card flagged as fraudulentCustomer must contact bank
payment_cancelled402Customer cancelled paymentCustomer action
payment_expired402Payment window expiredCreate new charge
expired_charge400Authorization expiredCreate new charge
brand_not_supported400Card brand not supportedUse supported card brand

Operation Errorsโ€‹

CodeHTTP StatusDescriptionSolution
failed_capture400Capture failedCheck charge state, may be already captured
failed_refund400Refund failedCheck charge state and refundable amount
failed_reverse400Reversal failedCheck charge state
failed_void400Void failedCheck charge state
failed_expire400Expire failedCheck charge state
failed_deletion422Deletion failedResource may have dependencies

Transfer/Recipient Errorsโ€‹

CodeHTTP StatusDescriptionSolution
invalid_bank_account400Invalid bank accountVerify bank account details
invalid_recipient400Invalid recipientVerify recipient exists and is active
invalid_transfer400Invalid transferCheck transfer parameters

Dispute Errorsโ€‹

CodeHTTP StatusDescriptionSolution
invalid_dispute400Invalid dispute operationCheck dispute state
documents_locked400Cannot modify documentsDispute is closed
invalid_file_type400Invalid file typeUse supported file formats
missing_file400File missingInclude file in upload

System Errorsโ€‹

CodeHTTP StatusDescriptionSolution
internal_error500Server-side errorRetry with backoff
backend_error400Payment provider errorRetry or contact support
search_unavailable503Search service downWait and retry
too_many_requests429Rate limit exceededImplement rate limiting
feature_not_supported400Feature not enabledContact support to enable
failed_multi_currency400Currency conversion failedCheck currency support

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โ€‹

  1. Go to Dashboard โ†’ Logs
  2. View all API requests and responses
  3. See exact error details
  4. 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:

  1. โœ… Copy API key from Dashboard (don't type manually)
  2. โœ… Check for extra spaces or line breaks
  3. โœ… Verify test vs live key matches environment
  4. โœ… Ensure Authorization header format: Basic base64(key:)

For Payment Errors:

  1. โœ… Try with test card numbers first
  2. โœ… Verify card details are correct
  3. โœ… Check if card supports online payments
  4. โœ… Test with different cards to isolate issue

For Validation Errors:

  1. โœ… Read error message carefully
  2. โœ… Check API documentation for parameter requirements
  3. โœ… Verify data types (number vs string)
  4. โœ… Check minimum/maximum values

For Server Errors:

  1. โœ… Check status.omise.co
  2. โœ… Retry after a delay
  3. โœ… Contact support if persistent
  4. โœ… 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

View all test cards โ†’


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:

  1. First retry: Wait 1 second
  2. Second retry: Wait 2 seconds
  3. Third retry: Wait 4 seconds
  4. Fourth retry: Wait 8 seconds
  5. 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โ€‹

StatusMeaningAction
200SuccessProcess response
400Bad RequestFix parameters
401UnauthorizedFix API key
402Payment FailedShow error, don't retry
404Not FoundVerify resource ID
422Invalid StateCheck resource state
429Rate LimitedImplement backoff
500Server ErrorRetry with backoff
503Service DownRetry 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


Next: Learn about Pagination to handle large result sets efficiently.