Refunds#
Process full and partial refunds for card and ACH transactions.
Overview#
The Refunds API allows you to return funds to customers for completed transactions. You can:
- 💰 Full refunds - Return the entire transaction amount
- 📊 Partial refunds - Return a portion of the transaction amount
- 🔄 Multiple refunds - Issue multiple partial refunds up to the original amount
- ⚡ Instant processing - Refunds are sent to the gateway immediately
- 🔁 Decline queue - Refunds declined by the gateway are queued in the Refund Decline Queue for retry
Each refund is recorded as a new transaction of type: "refund" with a REF-… ID and originalTransactionId pointing at the sale. The original sale's status becomes refunded once its full amount has been refunded; after a partial refund it keeps its status (approved or settled).
API Route Options#
| Auth Type | Endpoint | Use Case |
|---|---|---|
API Key (x-api-key) |
POST /api/v1/public/refund |
ERP / server integration |
| JWT Bearer | POST /api/v1/transactions/:id/refund |
Dashboard / admin (requires the transactions.refund permission) |
| JWT Bearer | POST /api/v1/transactions/:id/refund/partial |
Partial refund (amount required) |
| SDK / API key | POST /api/v1/auxvault/refund |
Official v2.0 SDK namespace; server-side x-api-key, SDK public keys get 403 |
For ERP and server-to-server integrations, see Refund a Transaction — Public API in the Transactions API.
Process a Refund (Admin API)#
Endpoint#
:transactionId is the transaction UUID or its TXN-… / ACH-… / CASH-… ID.
Full Refund#
Refund the entire remaining amount:
curl -X POST https://uat.auxcore.net/api/v1/transactions/7c9e6679-7425-40de-944b-e07fc1f90ae7/refund \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{}'Response (201):
{
"success": true,
"data": {
"id": "c2a8e1f4-3b7d-4e9a-8f12-6d5c4b3a2e10",
"transactionId": "REF-1769614200000-9F8E7D6C",
"type": "refund",
"status": "refunded",
"amount": "150.75",
"currency": "USD",
"originalTransactionId": "TXN-1769605200000-A1B2C3D4",
"responseCode": "100",
"responseText": "Approved",
"createdAt": "2026-01-28T15:30:00.000Z"
}
}Partial Refund#
Send an amount to refund part of the transaction. Either endpoint works:
curl -X POST https://uat.auxcore.net/api/v1/transactions/7c9e6679-7425-40de-944b-e07fc1f90ae7/refund/partial \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"amount": 50.00
}'The response has the same shape as a full refund, with amount set to the refunded value.
Request Parameters#
| Field | Type | Description | Required |
|---|---|---|---|
amount |
decimal | Refund amount (omit on /refund for a full refund; required and > 0 on /refund/partial) |
No |
A reason or metadata sent in the body is accepted but not stored.
Check the refund status#
The HTTP status is 201 and success is true whenever a refund row was created — including when the gateway declined it. Always read data.status:
data.status |
Meaning |
|---|---|
refunded |
Gateway accepted the refund |
declined |
Gateway refused it, or no gateway credentials were available — data.responseText says why. Only a gateway decline is added to the Refund Decline Queue |
TSYS Sierra exception: a full refund of a sale that has not settled yet is processed as a void, so data is the original sale with status: "voided" rather than a new REF-… row.
On POST /api/v1/public/refund only a lowercase amount makes the refund partial — a PascalCase Amount is ignored there and the full amount is refunded.
Multiple Partial Refunds#
You can issue multiple partial refunds until you reach the original transaction amount:
# First partial refund
curl -X POST https://uat.auxcore.net/api/v1/public/refund \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"transactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "amount": 30.00}'
# Second partial refund
curl -X POST https://uat.auxcore.net/api/v1/public/refund \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"transactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "amount": 20.00}'The system locks the original transaction, totals earlier refunded and pending refunds, and rejects any refund that would exceed the remaining amount.
Gateway-specific rules#
| Case | Behavior |
|---|---|
| Cash | Full refund only, recorded immediately as refunded (no gateway) |
| TSYS Sierra, not yet settled | A full refund is processed as a void; a partial refund returns 400 until the sale settles |
| Payliance ACH | Full refund only. A debit already sent to the bank (processing) can be refunded |
| Pacaso | Refund is requested from Pacaso by email and recorded as refunded with responseCode: "REQUESTED" |
Look Up Refunds#
There is no separate refunds resource — refunds are transactions.
Get a Refund#
curl https://uat.auxcore.net/api/v1/transactions/REF-1769614200000-9F8E7D6C \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id"With an API key, use GET /api/v1/public/transaction/:id.
List Refunds#
Send type=refund to return refunds only, e.g. GET /api/v1/transactions?type=refund&page=1.
Query Parameters#
| Parameter | Type | Description |
|---|---|---|
type |
string | refund |
page |
integer | Page number (default: 1) |
limit |
integer | Items per page (default: 50) |
dateFrom |
date | Filter by start date |
dateTo |
date | Filter by end date |
status |
string | Filter by status (refunded, declined, pending) |
curl "https://uat.auxcore.net/api/v1/transactions?type=refund&dateFrom=2026-01-01&status=refunded" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-ID: your-tenant-id"With an API key, use GET /api/v1/public/transaction-list?type=refund. The refunds for a given sale are the rows whose originalTransactionId equals the sale's transactionId; GET /api/v1/transactions/:id on the sale also returns its refundedAmount.
Refund Statuses#
| Status | Description |
|---|---|
refunded |
Refund processed successfully |
declined |
Refund declined by the processor, or no gateway credentials were available |
pending |
Refund in progress (only while the request is being processed) |
Declined Refunds (PRA)#
When the gateway declines a refund, it is added to the Refund Decline Queue. Declines flagged by the gateway as Protective Refund Amount (PRA) declines are also handed to the PRA handling flow.
Declined Response (201):
{
"success": true,
"data": {
"transactionId": "REF-1769614200000-9F8E7D6C",
"type": "refund",
"status": "declined",
"responseCode": "200",
"responseText": "Transaction was declined by processor",
"originalTransactionId": "TXN-1769605200000-A1B2C3D4"
}
}Each queue entry allows up to 3 retries, with exponential backoff between attempts. Retries run when the queue is processed (POST /api/v1/refund-decline-queue/process-retries, or POST /api/v1/refund-decline-queue/:id/retry for one entry) — no scheduler in AuxCORE triggers them on its own. A refund declined because no gateway credentials were available is not queued.
Learn more about Refund Decline Queue →
Refund via AuxCORE SDK#
curl -X POST https://uat.auxcore.net/api/v1/auxvault/refund \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"TransactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "Amount": 50.00}'Omit Amount for a full refund. Returns { "success": true, "Status": 1, "TransactionId": "…", "Amount": 50, "message": "Refund processed" }. success reflects only that the request was accepted — a gateway decline still returns Status: 1, so confirm the outcome by querying the transaction.
Refund Timeline#
| Event | Timeframe |
|---|---|
| Refund initiated | Immediate |
| Customer sees refund | 3-5 business days (card) / 3-5 business days (ACH) |
| Settlement | Next settlement batch |
⏰ Note: While refunds are processed immediately by AuxCORE, it takes 3-5 business days for funds to appear in the customer's account.
Webhooks#
Refund events carry the refund transaction in data (type: "refund", originalTransactionId set):
transaction.refunded#
{
"id": "0b6f3c2e-8a41-4d7e-9c55-2f1e7a9d3b64",
"type": "transaction.refunded",
"created": "2026-01-28T15:30:00.000Z",
"data": {
"transactionId": "REF-1769614200000-9F8E7D6C",
"type": "refund",
"status": "refunded",
"amount": "150.75",
"originalTransactionId": "TXN-1769605200000-A1B2C3D4",
"merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"metadata": { "tenantId": "your-tenant-id", "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }
}transaction.refund_failed#
{
"id": "5d2a9e71-3c84-4f0b-a6e2-9b7c1d4e8f20",
"type": "transaction.refund_failed",
"created": "2026-01-28T15:30:00.000Z",
"data": {
"transactionId": "REF-1769614200000-9F8E7D6C",
"type": "refund",
"status": "declined",
"amount": "150.75",
"responseText": "Transaction was declined by processor",
"originalTransactionId": "TXN-1769605200000-A1B2C3D4"
},
"metadata": { "tenantId": "your-tenant-id", "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }
}Best Practices#
✅ DO:#
- Verify transaction ID before refunding
- Check refundable amount before partial refunds (
refundedAmounton the sale) - Check
data.statuson every refund response - Handle declined refunds via the
transaction.refund_failedwebhook - Notify customers when refund is processed
- Track refund IDs (
REF-…) for reconciliation
❌ DON'T:#
- Don't refund more than original amount
- Don't refund voided transactions (they were never charged)
- Don't assume instant customer receipt
- Don't re-send a declined refund as a new refund — retry it through the Refund Decline Queue instead
Error Handling#
Refund errors are returned as HTTP 400 with error.code: "VALIDATION_ERROR", or 404 with NOT_FOUND.
Transaction Not Refundable#
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Only approved or settled transactions can be refunded"
}
}Refund Amount Exceeds Available#
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Refund amount ($50.00) exceeds remaining refundable amount ($20.00). Original: $100.00, previously refunded: $80.00."
}
}Transaction Not Found#
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Transaction with id TXN-0000000000000-INVALID0 not found"
}
}Other messages you may see: Valid refund amount is required (partial refund without a positive amount), Cash refunds support full refund only, Partial refunds on TSYS are only supported after settlement. …, and This transaction has already been lost to a chargeback and cannot be refunded. ….
Code Examples#
JavaScript#
async function processRefund(transactionId, amount = null) {
const response = await fetch('https://uat.auxcore.net/api/v1/public/refund', {
method: 'POST',
headers: {
'x-api-key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
transactionId,
...(amount && { amount })
})
});
const result = await response.json();
if (result.success && result.data.status === 'refunded') {
console.log('Refund processed:', result.data.transactionId);
} else if (result.success) {
console.warn('Refund declined:', result.data.responseText);
} else {
console.error('Refund failed:', result.error);
}
return result;
}Python#
def process_refund(transaction_id, amount=None):
payload = {'transactionId': transaction_id}
if amount:
payload['amount'] = amount
response = requests.post(
'https://uat.auxcore.net/api/v1/public/refund',
headers={
'x-api-key': api_key,
'Content-Type': 'application/json'
},
json=payload
)
result = response.json()
if result.get('success') and result['data']['status'] == 'refunded':
print(f"Refund processed: {result['data']['transactionId']}")
elif result.get('success'):
print(f"Refund declined: {result['data'].get('responseText')}")
else:
print(f"Refund failed: {result['error']}")
return resultNext Steps#
Need help? Contact support@auxcore.net