Error Codes
All errors from Gerbang Pay are returned in a consistent JSON format.
Error Response Format
json
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description"
}
}The code field is a constant string that you can use to handle errors programmatically. The message field is descriptive and may change — do not use it for conditionals in your code.
Error Codes Table
| HTTP Status | Error Code | Description | When It Occurs |
|---|---|---|---|
400 | BAD_REQUEST | Invalid request | Amount ≤ 0, currency is not 3 characters, invalid email format |
400 | PAYMENT_METHOD_UNAVAILABLE | Payment method not available | Method is not configured in the tenant's routing rules, or not supported by the provider |
401 | UNAUTHORIZED | Authentication failed | Invalid API key, expired or invalid JWT, missing Authorization header |
403 | FORBIDDEN | Access denied | Tenant is not a Client role when creating a payment, or accessing another tenant's resources |
404 | NOT_FOUND | Resource not found | Payment ID does not exist or belongs to a different tenant |
409 | CONFLICT | Data conflict | Data duplication or business rule violation |
409 | IDEMPOTENCY_CONFLICT | Idempotency key conflict | The same X-Idempotency-Key was submitted with a different payload |
409 | INVALID_STATE_TRANSITION | Invalid state transition | Attempting to cancel a payment that is already paid or expired |
429 | RATE_LIMIT_EXCEEDED | Rate limit exceeded | Too many requests in a short period |
500 | INTERNAL_ERROR | Internal server error | Unexpected internal failure. Transient — safe to retry with exponential backoff |
502 | PROVIDER_ERROR | Error from external provider | Winpay or Midtrans returned an error |
503 | CIRCUIT_BREAKER_OPEN | Circuit breaker open | Provider is failing repeatedly in a short period — wait a few minutes before retrying |
504 | PROVIDER_TIMEOUT | Provider timeout | Winpay or Midtrans did not respond within the time limit |
504 | REQUEST_TIMEOUT | Global request timeout | Request exceeded the server time limit |
💡 Note on
5xxerrors: Errors with HTTP statuses500,502,503, and504are transient (temporary). Implement a retry with exponential backoff mechanism on your client side for these cases.
Example Error Responses
400 Bad Request — Invalid Amount
json
{
"success": false,
"error": {
"code": "BAD_REQUEST",
"message": "bad request: Amount must be greater than zero"
}
}401 Unauthorized — Invalid API Key
json
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "unauthorized: Invalid API Key"
}
}403 Forbidden — Non-Client Role
json
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "forbidden: Only client tenants are authorized to process payments"
}
}404 Not Found — Payment Not Found
json
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "not found: payment with id 550e8400-e29b-41d4-a716-446655440000"
}
}409 Idempotency Conflict
json
{
"success": false,
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "idempotency conflict: Same key submitted with different payload"
}
}409 Invalid State Transition
json
{
"success": false,
"error": {
"code": "INVALID_STATE_TRANSITION",
"message": "invalid state transition: from paid to cancelled"
}
}400 Payment Method Unavailable
json
{
"success": false,
"error": {
"code": "PAYMENT_METHOD_UNAVAILABLE",
"message": "payment method unavailable: No routing rule configured for e_wallet"
}
}429 Rate Limit Exceeded
json
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Please slow down and retry."
}
}502 Provider Error
json
{
"success": false,
"error": {
"code": "PROVIDER_ERROR",
"message": "provider error [winpay]: Code: 400 | Status: Bad Request | Body: ..."
}
}503 Circuit Breaker Open
json
{
"success": false,
"error": {
"code": "CIRCUIT_BREAKER_OPEN",
"message": "circuit breaker open for provider: winpay"
}
}Payment State Machine
Payments have statuses that follow a strict state machine. Allowed transitions:
pending → paid
pending → failed
pending → expired
pending → cancelled
pending → challenged
challenged → paid
challenged → failed
paid → paid (duplicate callback, ignored)
paid → refunding
paid → partially_refunded
partially_refunded → partially_refunded
partially_refunded → refunding
refunding → refundedAttempting a transition outside of those permitted will result in an INVALID_STATE_TRANSITION error (HTTP 409).
Error Handling Tips
- Always check the
codefield for retry/branching logic, not themessagefield. 5xxErrors are generally temporary — implement retries with exponential backoff.PROVIDER_ERROR(502) means the external provider is experiencing issues — try again in a moment.CIRCUIT_BREAKER_OPEN(503) — the provider is in recovery mode, wait a few minutes.IDEMPOTENCY_CONFLICT(409) — do not retry with the same key if the payload differs, use a new key.PAYMENT_METHOD_UNAVAILABLE(400) — ensure routing rules are configured in the dashboard for the method being used.