PRA Refund Decline Queue#
Track and retry declined refunds (including PRA — Protective Refund Amount — declines).
Overview#
When a refund is declined by the gateway, AuxCORE adds it to the Refund Decline Queue so it can be retried or resolved later:
- 📥 Automatically queued - Every declined refund is added (one open entry per refund)
- 🔄 Retry with backoff - Up to 3 retries, 5 → 10 → 20 minutes apart — but only when something calls
process-retries(nothing schedules it today) - ✅ Auto-resolution - Marked
resolvedwhen the retry's refund call returns successfully (see the caveat under Manual Retry) - 🛠️ Manual control - Retry, resolve, or cancel anytime
All endpoints are admin API routes: Authorization: Bearer <JWT> (or an API key in x-api-key).
How It Works#
1. Refund Attempted → 2. Declined → 3. Added to Queue (pending)
↓
4. Resolved ← Retry (process-retries or manual) → retry → … → failed after 3 failed retriesList Queue Entries#
Endpoint#
Query Parameters#
| Parameter | Type | Description |
|---|---|---|
status |
string | Filter by status (see below) |
merchantId |
string | Filter by merchant |
fromDate |
date | Created on/after |
toDate |
date | Created on/before |
page |
integer | Page number (default 1) |
limit |
integer | Items per page (default 50) |
curl "https://uat.auxcore.net/api/v1/refund-decline-queue?status=pending" \
-H "Authorization: Bearer YOUR_TOKEN"Response:
{
"success": true,
"data": {
"entries": [
{
"id": "2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d",
"tenantId": "your-tenant-id",
"merchantId": "3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90",
"transactionId": "TXN-REF-000789",
"originalTransactionId": "TXN-000456",
"amount": 150.75,
"gatewayType": "NMI",
"status": "retry",
"declineCode": "300",
"declineReason": "Refund declined",
"gatewayResponse": { "responsetext": "Refund declined" },
"retryCount": 1,
"maxRetries": 3,
"scheduledRetryAt": "2026-01-28T15:15:00Z",
"lastRetryAt": "2026-01-28T15:05:00Z",
"lastRetryError": "Refund declined",
"resolvedAt": null,
"resolvedBy": null,
"resolutionNotes": null,
"createdAt": "2026-01-28T15:00:00Z",
"updatedAt": "2026-01-28T15:05:00Z"
}
],
"total": 15,
"page": 1,
"limit": 50
}
}Entries are ordered newest first. There is no single-entry GET /:id endpoint — filter the list instead.
Queue Entry Statuses#
| Status | Description |
|---|---|
pending |
Queued, waiting for its first retry |
processing |
A retry is in progress |
retry |
A retry failed; waiting for the next one |
resolved |
Refunded by a retry, or resolved manually |
failed |
All retries used up (retryCount reached maxRetries) |
cancelled |
Manually cancelled |
Manual Retry#
Retry one entry now. The refund is re-sent for the original transaction through the same gateway.
Endpoint#
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d/retry \
-H "Authorization: Bearer YOUR_TOKEN"Response (refund call returned successfully):
{
"success": true,
"data": { "success": true, "message": "Refund processed successfully" },
"message": "Refund retry initiated successfully"
}A manual retry counts toward maxRetries like an automatic one — it does not reset the counter.
Caveat: the entry is marked
resolvedwhenever the internal refund call returnssuccess: true. That call also returnssuccess: truewhen it creates a new refund that the gateway declines again, so aresolvedentry withresolutionNotes: "Automatic retry successful"does not prove the money was refunded. Check the original transaction's refunds before closing the case with the customer.
| Status | When |
|---|---|
400 |
VALIDATION_ERROR — entry already resolved, already failed, or max retries reached (a cancelled entry is not blocked) |
404 |
Entry not found |
500 |
INTERNAL_ERROR — the refund call errored (non-2xx or timeout); the entry moves to retry (or failed on the last attempt) |
Manual Resolution#
Mark an entry as resolved (e.g., refunded through a different method).
Endpoint#
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d/resolve \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "notes": "Refunded via check" }'notes is required (400 VALIDATION_ERROR otherwise). The response data is the updated entry with status: "resolved", resolvedAt, resolvedBy (your user id) and resolutionNotes, plus "message": "Refund resolved successfully".
Cancel Entry#
Cancel a queue entry (e.g., customer no longer wants the refund).
Endpoint#
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d/cancel \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reason": "Customer request" }'reason is required (400 VALIDATION_ERROR otherwise). The response data is the updated entry with status: "cancelled"; the reason is stored in resolutionNotes.
Retry Schedule#
Each entry allows 3 retries (maxRetries). The next retry is scheduled at 5 minutes × 2^retryCount, capped at 24 hours:
| Retry | Scheduled after |
|---|---|
| 1 | 5 minutes after the decline |
| 2 | 10 minutes after retry 1 fails |
| 3 | 20 minutes after retry 2 fails |
These are only the scheduledRetryAt times; nothing runs a retry at that time unless process-retries is called (see below). After the third failed retry the entry becomes failed and needs manual action (resolve or cancel).
Process Retries#
Runs every due retry: entries in pending or retry whose scheduledRetryAt has passed and that still have retries left.
Endpoint#
curl -X POST https://uat.auxcore.net/api/v1/refund-decline-queue/process-retries \
-H "Authorization: Bearer YOUR_TOKEN"Response:
{
"success": true,
"data": {
"processed": 5,
"successful": 2,
"failed": 3,
"errors": [
{ "id": "2c4e6a8b-1d3f-4a5b-9c7d-8e9f0a1b2c3d", "error": "Refund declined" }
]
},
"message": "Processed 5 refunds: 2 successful, 3 failed"
}Note: No scheduler calls this endpoint automatically today. Scheduled retries only run when
process-retries(or a manual retry) is called.
Webhooks and Analytics#
Note: There are no
refund_decline_queue.*webhook events and no/refund-decline-queue/statsendpoint. A declined refund raises the standardtransaction.refund_failedwebhook (see Advanced Webhooks); use the list endpoint withstatusfilters for counts.
Best Practices#
✅ DO:#
- Monitor the queue daily - Check for
failedentries - Subscribe to
transaction.refund_failed- Get notified of declines - Document resolutions - Notes are required and kept on the entry
❌ DON'T:#
- Don't ignore failed entries - Follow up with customers
- Don't cancel prematurely - Give retries time to run
Troubleshooting#
Queue Not Processing#
If scheduled retries aren't happening, make sure something calls POST /process-retries (see the note above), then check the entry's scheduledRetryAt and retryCount.
Stuck Entries#
If an entry stays in processing, the retry call did not complete. Use Manual Resolution or Cancel once you have confirmed the refund state with the gateway.
Next Steps#
Need help? Contact support@auxcore.net