Checking access…

Skip to main content
Version: v2

Common Error Patterns

πŸ”’ InternalInternal information β€” not visible in the public (merchant) site.

Overview​

This document catalogs the error patterns that engineers encounter across v2 implementations. Each pattern follows a symptom β†’ diagnosis β†’ resolution structure and applies broadly across domains. Domain-specific error details (e.g., payment decline codes) are documented in each domain guide.


HTTP 400 β€” Schema Validation Errors​

Pattern: Invalid JSON or Missing Fields​

Symptom:

{ "title": "INVALID_REQUEST", "status": 400, "detail": "Invalid request format. Please check JSON structure and field types" }

Common Causes:

  • Malformed JSON body (syntax errors, trailing commas, unquoted strings)
  • Missing required fields
  • Type mismatches (e.g., sending a string for an integer field)
  • Constraint violations (e.g., amount: 0 when minimum is 1, string exceeding maxLength)

Resolution:

  1. Validate the request body against the OpenAPI spec for the endpoint
  2. Use a JSON linter to verify syntax
  3. Ensure monetary amounts are integers in cents, not dollars
  4. Verify UUIDs are properly formatted (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

HTTP 401 β€” Unauthorized​

Pattern: Missing or Invalid Token​

Symptom:

{ "title": "UNAUTHORIZED", "status": 401 }

Common Causes:

  • OAuth2 token missing from Authorization header
  • Token expired
  • Token issued for a different environment

Resolution:

  1. Verify the Authorization: Bearer <token> header is present
  2. Check token expiry β€” re-authenticate if expired
  3. Confirm the token was issued for the correct environment (prod vs non-prod)

HTTP 403 β€” Forbidden​

Pattern: Merchant Not Linked to Token​

Symptom:

{ "title": "FORBIDDEN", "status": 403, "detail": "403 FORBIDDEN. RequestId: abc123-def456" }

Common Causes:

  • X-Merchant-Id header value is not associated with the OAuth2 token's clientId
  • Using a test token with a production merchant ID, or vice versa

Resolution:

  1. Verify the X-Merchant-Id value is correct for the environment
  2. Confirm the OAuth2 token was generated with the correct clientId
  3. Check merchant-to-client mapping in the admin portal

HTTP 404 β€” Not Found​

Pattern: Resource Does Not Exist or Wrong Merchant​

Symptom:

{ "title": "NOT_FOUND", "status": 404, "detail": "<resource> not found" }

Common Causes:

  • The resource ID does not exist in the system
  • The resource exists but belongs to a different merchantId
  • The resource was created in a different environment (dev vs stage)

Resolution:

  1. Verify the resource ID is correct (check UUID format, no typos)
  2. Confirm the X-Merchant-Id matches the merchant that created the resource
  3. Confirm the X-Upstream-Env header targets the correct environment

HTTP 422 β€” Business Rule Violation​

Pattern: Unprocessable Entity​

Symptom:

{ "title": "UNPROCESSABLE_ENTITY", "status": 422, "detail": "<scenario-specific message>" }

Common Causes:

  • Invalid state transition (e.g., attempting to cancel a resource that is already in a terminal state)
  • Idempotency key retry limit exceeded (> 5 attempts with the same key)
  • Cross-field validation failure that passes schema validation but violates business rules

Resolution:

  1. Check the current resource state via GET /v2/{resource}/{id}
  2. Refer to the domain-specific state transition rules
  3. If idempotency key exhausted, generate a new unique key

Pattern: External Processing Error​

Symptom:

{ "title": "PAYMENT_ERROR", "status": 422, "detail": "Processing failed for all records. Check individual records for error details", "data": { ... } }

Common Causes:

  • External vendor (Stripe) declined the transaction
  • Insufficient funds, expired card, or fraud detection
  • Vendor-side validation failure

Resolution:

  1. Check the error object within each allocation/record in the data field
  2. The error.code and error.message come from the external vendor
  3. In test environments, verify you are using the vendor's test credentials and test data

HTTP 500 β€” Internal Server Error​

Pattern: Unexpected Failure or Vendor Timeout​

Symptom:

{ "title": "INTERNAL_ERROR", "status": 500, "detail": "Internal Error" }

Common Causes:

  • Unhandled exception in the service
  • External vendor API timeout
  • Database connectivity issue

Resolution:

  1. Check service logs in Splunk β€” filter by requestId from the response headers
  2. Check Azure Application Insights for exception traces
  3. If vendor-related, check the vendor's status page
  4. If the issue persists, check circuit breaker state in service metrics

Cross-Cutting: Environment & Configuration Errors​

Pattern: Requests Reaching Wrong Environment​

Symptom: Resources created in one environment are not found in another; unexpected 404 errors.

Diagnosis: The X-Upstream-Env header is either missing or set to the wrong value.

Resolution: In non-production environments, always include X-Upstream-Env: dev|stage|test. In production, this header must not be sent.

Pattern: Stale Merchant Configuration​

Symptom: Requests fail with business rule violations that should succeed, or features appear disabled.

Diagnosis: Merchant configuration may have been updated but not yet propagated to the service.

Resolution: Verify merchant settings via the admin portal and allow time for config propagation. If urgent, check the wallet-merchant-service logs for the latest config event.


Debugging Workflow​

For any unresolved error, follow this workflow:


Observability: Diagnosing with Splunk & Application Insights​

Splunk β€” Key Searches​

All services emit structured JSON logs forwarded to Splunk. Use field-based search for fast lookups.

Trace a full request by requestId:

index=wallet requestId="<correlation-id>"
| sort _time
| table _time, service, level, message, status

Find all errors for a specific payment:

index=wallet source="wallet-payment-service" level=ERROR paymentId="<uuid>"
| table _time, level, message, errorCode, merchantId

Find all PaymentFailedException events in the last hour:

index=wallet source="wallet-payment-service" level=ERROR "PaymentFailedException"
| stats count by message
| sort -count

Find slow vendor calls (>2s):

index=wallet source="wallet-payment-service" duration>2000
| table _time, message, duration, vendorName
| sort -duration

Azure Application Insights β€” Key KQL Queries​

Find all failed requests in the last hour:

requests
| where timestamp > ago(1h)
| where success == false
| project timestamp, name, resultCode, duration, operation_Id
| order by timestamp desc

Trace a full request end-to-end by operation_Id:

union requests, dependencies, exceptions, traces
| where operation_Id == "<your-operation-id>"
| order by timestamp asc
| project timestamp, itemType, name, message, severityLevel, duration, resultCode

Top exceptions by count over last 24h:

exceptions
| where timestamp > ago(24h)
| summarize count() by type, outerMessage
| order by count_ desc

Find all 422 payment failures:

requests
| where timestamp > ago(24h)
| where name contains "payments" and resultCode == "422"
| project timestamp, name, resultCode, customDimensions
| order by timestamp desc

Log Injection Prevention​

All user-controlled input must be sanitized via LogSanitizationUtil.sanitizeForLog() before logging to prevent log forging via \n/\r characters in user data:

// Correct β€” sanitize before logging
log.info("Processing payment for merchantTransactionId: {}",
LogSanitizationUtil.sanitizeForLog(paymentRequest.getMerchantTransactionId()));

log.error("Payment failed - PaymentId: {}, Message: {}",
LogSanitizationUtil.sanitizeForLog(
ex.getPaymentResponse() != null ? ex.getPaymentResponse().getId() : null),
ex.getMessage(), ex);

// Incorrect β€” never log raw user input directly
log.info("MerchantTxId: {}", paymentRequest.getMerchantTransactionId()); // ❌