Unlinked Refunds API
Unlinked Refund allows merchants to issue a refund directly to a payment method (typically a card) without referencing a prior payment transaction. Unlike linked refunds that reverse a specific charge, unlinked refunds credit a payment method independently β useful for goodwill credits, resolving failed linked refunds, or special circumstances. This guide covers how to initiate unlinked refunds, monitor status, and handle errors and webhook notifications.
Standard refunds? For refunds tied to an existing payment, use linked refunds β see Split Tender Refunds. Linked refunds reference a
paymentIdand can be partial or full based on the original payment amount.
- Refund model: Direct credit to payment method using
paymentMethodId - Payment method types: CARD only β ACH and bank accounts not supported
- Single refund per transaction: Only one unlinked refund allowed per (
merchantId,merchantTransactionId) pair - Customer required: Explicit customer object with identifiers must be provided
When to Useβ
Use Unlinked Refund when:
- No prior payment exists β issuing a goodwill credit or promotional refund
- Linked refund failed β original payment is unavailable, expired, or disputed
- Special circumstances β manual resolution requires a direct credit to customer's card
- Card replacement β customer has a new card and the old payment cannot be reversed
Note: Unlinked refunds are CARD-only and single-use per merchant transaction ID. The customer and payment method must be properly associated before processing.
For business context, eligibility, scenarios see Unlinked Refunds β Business Document.
- API Endpoint:
POST v2/refundsβ create an unlinked refundGET v2/refunds/{refundId}β poll refund status
- Scope: All operations are at the
merchantscope. - API Specs: Create Refund | Get Refund
- Webhook Specs: Refund Webhooks
- API:
v2 - Testing: Use Test Cards to simulate unlinked refund scenarios. See Test Cards
- β οΈ Error Handling: See Refund Error Codes for unlinked-specific errors and resolution steps
Refund Processing Flowβ
Unlinked refunds are processed independently without referencing any prior payment transaction.
Refund Flow
Refund Processing Steps:
- Request received β Payment Service receives unlinked refund request with
paymentMethodIdandcustomer. - Schema validation β Payment Service validates request structure and required fields β
400 INVALID_REQUESTon failure. - Business validation β Payment Service enforces business rules β
422 UNPROCESSABLE_ENTITYon failure. - Payment method validation β Payment Service calls Customer Service to get payment method details and checks if CARD type (rejects BANK/ACH).
- Customer resolution β Payment Service searches for customer in Customer Service; if not found, creates customer via
/customer/findand sends event to Stripe. - Association validation β Payment Service verifies customer and payment method are associated via Customer Service.
- Acceptance β If all validations pass, Payment Service returns
202 Accepted. - Refund execution β Payment Service sends refund request to Stripe Adapter Service, which executes unlinked refund in Stripe (without charge reference).
- Stripe events β Stripe publishes refund events (
refund.created,refund.updated, orrefund.failed) to Stripe Adapter Service. - Status update β Stripe Adapter Service sends
VENDOR_MERCHANT_REFUND_SUCCESSorVENDOR_MERCHANT_REFUND_FAILEDto Payment Service, which updates refund status. - Merchant notification β Payment Service publishes event to Merchant Service, which sends
REFUND_SUCCESSorREFUND_FAILEDwebhook to Merchant. - Status polling β Merchant can poll
GET /v2/refunds/{refundId}to retrieve refund details.
API Requestβ
Sample Request β POST v2/refunds
curl -X POST "https://api-stg.uhg.com/api/financial/commerce/checkout/v2/refunds" \
-H "Authorization: <token>" \
-H "X-Merchant-Id: <merchantId>" \
-H "Content-Type: application/json" \
-d '{
"reason": "REQUESTED_BY_CUSTOMER",
"merchantTransactionId": "6f76fa67-b887-4c36-a486-b425b86befbd",
"customer": {
"firstName": "string",
"lastName": "string",
"hsid": "50daa5d5-37a1-48e8-91be-efd1743dbc2e",
"metadata": {}
},
"refundAllocations": [
{
"amount": 100,
"paymentMethodId": "09584dad-194e-455a-b980-0bb3abf10fe4"
}
]
}'
Request Field Detailsβ
| Field | Required | Type | Description |
|---|---|---|---|
amount | Yes | Number | Refund amount in cents (e.g., 5000 = $50.00). Must not exceed $10,000 USD (1000000). |
reason | Yes | String | Reason code (e.g., DUPLICATE, FRAUDULENT, REQUESTED_BY_CUSTOMER) |
paymentMethodId | Yes | UUID | Payment method to be credited (must be CARD type) |
merchantTransactionId | Yes | UUID | Unique merchant reference for idempotency and tracking |
customer | Yes | Object | Customer information for validation |
customer.firstName | Yes | String | Customer first name |
customer.lastName | Yes | String | Customer last name |
customer.hsid | Yes | String | Customer HSID identifier (primary identifier) |
customer.metadata | No | Object | Additional customer metadata |
paymentIdmust be absent or null for unlinked refunds- If both
paymentIdandpaymentMethodIdare provided, the request is treated as a linked refund - Payment method must be CARD type β ACH and bank accounts will be rejected with
422 UNPROCESSABLE_ENTITY - Customer must exist and payment method must be associated with that customer
Poll Refund Statusβ
Refund processing is asynchronous β the API returns INITIATED immediately. Poll GET v2/refunds/{refundId} until the status settles to a terminal state (COMPLETED or FAILED).
Sample Request β GET v2/refunds/refundId
curl -X GET "https://api-stg.uhg.com/api/financial/commerce/checkout/v2/refunds/{refundId}" \
-H "Authorization: <token>" \
-H "X-Merchant-Id: <merchantId>" \
-H "Accept: application/json"
API Responseβ
The complex object is trimmed for brevity. Refer to the API Spec for the full response structure.
- Unlinked refunds do not include
paymentobject since there's no associated payment - The
paymentMethodobject contains the credited card details - Top-level
statusshows refund outcome:COMPLETED,FAILED, orPENDING - For
FAILED, check theerrorobject for details - No
refundAllocationsarray for unlinked refunds β single direct credit - For all error codes and troubleshooting, see Refund Error Codes.
Final Response β COMPLETEDβ
Sample Response β COMPLETED (HTTP 200)
{
"url": "http://api-stg.uhg.com:443/api/financial/commerce/nonprodcheckout/v2/refunds/1b9683da-5f0b-4444-8c5a-3958bd67353f",
"data": {
"id": "1b9683da-5f0b-4444-8c5a-3958bd67353f",
"status": "COMPLETED",
"reason": "REQUESTED_BY_CUSTOMER",
"merchantTransactionId": "6f76fa67-b887-4c36-a486-b425b86befbd",
"metadata": {
"orderId": "123486",
"purpose": "issueCredit"
},
"merchant": {
"id": "b955db5e-aef2-47de-bbb9-c80b9cc16e8f"
},
"refundAllocations": [
{
"id": "f771d07c-c827-41a4-8dbd-6791ff390f00",
"amount": 100,
"status": "COMPLETED",
"paymentMethod": {
"id": "09584dad-194e-455a-b980-0bb3abf10fe4",
"nickname": "string",
"paymentMethodDetails": {
"type": "CARD",
"last4": "4242",
"expiryMonth": 12,
"expiryYear": 2045,
"nameOnCard": "string",
"cardBrand": "VISA",
"status": "ACTIVE",
"zipCode": "12345",
"cardCategories": []
},
"default": true
}
}
]
}
}
Final Response β FAILED (HTTP 422)β
Sample Response β FAILED (HTTP 422)
{
"title": "REFUND_ERROR",
"detail": "Refund allocation processing failed for all records. Check individual records for error details",
"status": 422,
"refund": {
"id": "7dd80cba-910e-493f-9eb3-20f8324a1dcc",
"status": "FAILED",
"reason": "REQUESTED_BY_CUSTOMER",
"merchantTransactionId": "1f401160-e825-415f-81af-056fac57bba4",
"metadata": {},
"merchant": {
"id": "b955db5e-aef2-47de-bbb9-c80b9cc16e8f"
},
"refundAllocations": [
{
"id": "c4f347e5-a4fa-40de-883e-00cfa6352e31",
"amount": 99999999,
"status": "FAILED",
"error": {
"title": "REFUND_ERROR",
"detail": "The amount given exceeds the allowed refund limit"
},
"paymentMethod": {
"id": "09584dad-194e-455a-b980-0bb3abf10fe4",
"nickname": "string",
"paymentMethodDetails": {
"type": "CARD",
"last4": "4242",
"expiryMonth": 12,
"expiryYear": 2045,
"nameOnCard": "string",
"cardBrand": "VISA",
"status": "ACTIVE",
"zipCode": "12345",
"cardCategories": []
},
"default": true
}
}
]
}
}
Status Handlingβ
Polling Guidelinesβ
Refund processing is asynchronous β the API returns INITIATED immediately. Poll GET v2/refunds/{refundId} until the status settles to a terminal state (COMPLETED or FAILED).
- β
Supported:
- Poll the refund using
refundIdonly. - Get real-time status in a single response.
- See error details when refund fails.
- Poll the refund using
- β Not Supported:
- Polling by
merchantTransactionId(not available).
- Polling by
Always use the refund refundId for polling. Polling by merchantTransactionId is not supported and will result in API errors.
Status Definitionsβ
Unlinked refunds have simpler status transitions compared to split tender refunds since they process as a single operation.
Refund Status Definitions
| Status | Description |
|---|---|
INITIATED | Refund request accepted; async validation and submission started |
PENDING | Refund is being processed by the payment processor (Stripe) |
COMPLETED | Refund processed successfully; funds returned to card |
FAILED | Refund failed; no funds returned |
Note:
PENDINGis optional β refunds may transition directly fromINITIATED β COMPLETEDorINITIATED β FAILED.
Status Lifecycleβ
Refund Lifecycleβ
Unlinked refunds move through these states:
Status Flowβ
Error Handlingβ
For the complete list of error codes, status codes, and error details, see Refund Error Codes.
Unlinked RefundβSpecific Errorsβ
Beyond standard refund errors, unlinked refunds have these specific failure modes:
- CARD-only restriction: Payment method type must be CARD β ACH and bank accounts are rejected with
422 UNPROCESSABLE_ENTITY: Unlinked refunds are only supported for CARD payment methods. - Customer not found: Customer HSID does not exist in the system β
422 UNPROCESSABLE_ENTITY: Customer not found. - Payment method not associated: Payment method does not belong to the specified customer β
422 UNPROCESSABLE_ENTITY: Payment method not associated with customer. - Duplicate transaction: Same
merchantTransactionIdalready used for an unlinked refund β422 UNPROCESSABLE_ENTITY: Duplicate merchant transaction. - Amount exceeds limit: Refund amount exceeds $10,000 USD β
422 UNPROCESSABLE_ENTITY: Amount exceeds maximum allowed. - Invalid or expired card: Card is invalid, expired, or deleted β Stripe rejects with failure details.
Validation Layersβ
Unlinked refunds go through strict two-layer validation:
| Layer | Error Code | Examples |
|---|---|---|
| Schema Validation | 400 INVALID_REQUEST | Missing fields, invalid structure, wrong data types |
| Business Validation | 422 UNPROCESSABLE_ENTITY | CARD-only violation, duplicate transaction, customer mismatch |
Webhooksβ
CCG sends one webhook per refund request. Subscribe to these events to get real-time refund outcomes without polling.
Webhook Eventsβ
- onRefundSuccess β full payload schema for success events
- onRefundFailed β full payload schema for failure events
| Event | Trigger | Notes |
|---|---|---|
REFUND_SUCCESS | Refund completed successfully | Funds returned to card |
REFUND_FAILED | Refund failed | No funds returned |
REFUND_PENDING | Refund queued (rare) | Awaiting processor balance |
Unlinked refund webhooks do not include a paymentId field since there's no associated payment. The payload includes:
refundIdmerchantTransactionIdamountstatusvendorMerchantCustomerIdpaymentMethod(optional β may not always be returned by Stripe)
Stripe(Payment Processor) Considerationsβ
- Customer creation: CCG creates or finds the customer in Stripe before executing the refund
- Refund execution: Stripe processes unlinked refunds via
POST /v1/refundswithout achargeparameter - Detection: Unlinked refunds detected via
refund.charge == nullin Stripe webhook events - Timing: Card refunds typically process in 1β3 business days
- Balance requirements: If Stripe account has insufficient balance and
autodebitis disabled, refund may be held inPENDINGstatus
Retry Handlingβ
Unlinked refunds do not support automatic retries. Each refund request is a distinct operation:
- Failed refund: If validation fails or processor rejects, merchant must handle manually:
- Fix validation errors (e.g., use correct CARD payment method) and resubmit with new
merchantTransactionId. - Verify customer and payment method association before retry.
- Check payment method status (active, not expired, not deleted).
- Fix validation errors (e.g., use correct CARD payment method) and resubmit with new
- Duplicate prevention: Same
merchantTransactionIdcannot be reused β always use a new unique ID for retry attempts. - Customer resolution: If customer not found, ensure customer exists in system before retry.
Recovery Steps for Failed Refundsβ
| Situation | Action |
|---|---|
| Customer not found | Verify customer HSID and ensure customer exists in system |
| Payment method not associated | Check payment method belongs to customer; update association |
| Invalid payment method type | Use CARD payment method only; ACH not supported |
| Expired or deleted card | Use alternate payment method or manual credit outside CCG |
| Amount exceeds limit | Reduce amount to $10,000 USD or less |
| Duplicate transaction ID | Generate new unique merchantTransactionId for retry |
| Stripe customer creation failed | Check Stripe account configuration; contact support if persists |
V1 vs V2 API Comparisonβ
Unlinked Refund is supported in both v1 and v2 APIs, but v2 provides enhanced validation and clearer error handling. This section helps understand the differences and migrate from v1 to v2.
Key Differencesβ
| Aspect | V1 API (POST /v1/refund) | V2 API (POST /v2/refunds) |
|---|---|---|
| Validation | Basic validation only | Strict two-layer validation (Schema + Business) |
| Error Messages | Generic error responses | Detailed, actionable error codes |
| Customer Required | Yes (in request body) | Yes (in request body with HSID) |
| Amount Limit | $10,000 USD | $10,000 USD |
| Duplicate Detection | Limited | Strict enforcement per merchant transaction ID |
| Webhook Events | Same event names | Same event names, better payload structure |
| Status | INITIATED, COMPLETED, FAILED | INITIATED, PENDING, COMPLETED, FAILED |
Version-Specific Documentationβ
FAQβ
Q: What's the difference between linked and unlinked refunds?
- Linked refunds reference a
paymentIdand reverse a specific charge. Unlinked refunds usepaymentMethodIdand credit a card directly without a prior payment.
Q: Can I use unlinked refunds for ACH or bank accounts?
- No. Unlinked refunds are CARD-only. ACH and bank accounts are not supported and will be rejected with
422 UNPROCESSABLE_ENTITY.
Q: Why is customer information required?
- Customer validation ensures the payment method belongs to the intended recipient and provides proper audit trail and fraud prevention.
Q: Can I issue multiple unlinked refunds to the same card?
- Yes, but each must use a unique
merchantTransactionId. You cannot reuse the same transaction ID.
Q: What happens if the customer doesn't exist?
- The request fails with
422 UNPROCESSABLE_ENTITY: Customer not found. Ensure the customer exists in the system before attempting an unlinked refund.
Q: What if the payment method is not associated with the customer?
- The request fails with
422 UNPROCESSABLE_ENTITY: Payment method not associated with customer. Verify the association before retry.
Q: When is a refund status final?
COMPLETEDandFAILEDare terminal states.INITIATEDandPENDINGare transient.
Q: How do I correlate webhook events with refund records?
- Use the
refundIdormerchantTransactionIdin both the API response and webhook payload to match events.
Q: What if I don't receive a webhook?
- Poll
GET v2/refunds/{refundId}to get the current status. Check your endpoint logs and ensure it returns a2xxstatus. See Webhook Configuration.
Q: Can I issue an unlinked refund exceeding $10,000?
- No. The maximum amount per unlinked refund is $10,000 USD (1,000,000 cents). Requests exceeding this limit are rejected.
Integration Checklistβ
- Merchant account is configured and has access to refund APIs
- API endpoints and authentication are set up
- Refund request uses
paymentMethodId(notpaymentId) for unlinked refunds - Customer object includes required fields:
firstName,lastName,hsid - Payment method is CARD type (not ACH or bank account)
- Customer and payment method are properly associated in system
- Each refund uses a unique
merchantTransactionId - Amount does not exceed $10,000 USD per refund
- Status polling uses
refundIdβ notmerchantTransactionId - Webhook endpoint is configured and returns
2xx - Webhook handler is idempotent and logs all payloads
- All error codes and validation failures are handled
- Integration tested with test cards and simulated failures