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: 0when minimum is 1, string exceedingmaxLength)
Resolution:
- Validate the request body against the OpenAPI spec for the endpoint
- Use a JSON linter to verify syntax
- Ensure monetary amounts are integers in cents, not dollars
- 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
Authorizationheader - Token expired
- Token issued for a different environment
Resolution:
- Verify the
Authorization: Bearer <token>header is present - Check token expiry — re-authenticate if expired
- 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-Idheader value is not associated with the OAuth2 token'sclientId- Using a test token with a production merchant ID, or vice versa
Resolution:
- Verify the
X-Merchant-Idvalue is correct for the environment - Confirm the OAuth2 token was generated with the correct
clientId - 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 (
devvsstage)
Resolution:
- Verify the resource ID is correct (check UUID format, no typos)
- Confirm the
X-Merchant-Idmatches the merchant that created the resource - Confirm the
X-Upstream-Envheader 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:
- Check the current resource state via
GET /v2/{resource}/{id} - Refer to the domain-specific state transition rules
- 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:
- Check the
errorobject within each allocation/record in thedatafield - The
error.codeanderror.messagecome from the external vendor - 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:
- Check service logs in Splunk — filter by
requestIdfrom the response headers - Check Azure Application Insights for exception traces
- If vendor-related, check the vendor's status page
- 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()); // ❌