Skip to main content
Version: v2

Common Error Patterns

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()); // ❌