Void Authorization
Overview
Void an authorization to release the hold on funds and cancel the transaction. Voiding prevents the authorization from being captured and returns the funds to the cardholder. Depending on the issuing bank, voids can take up to 7 days to fully release the hold.
Resource Access
- User Permissions: Merchant users with authorization permissions
- Endpoint:
PUT /authorizations/\{authorization_id}
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| authorization_id | string | Yes | ID of the authorization to void |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| void_me | boolean | Yes | Set to true to void the authorization |
Example Request
curl -X PUT \
'https://api.ahrvo.network/payments/na/authorizations/AUh4LG8REtmRBCxaSp85PMkr' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"void_me": true
}'
Example Response
{
"id": "AUh4LG8REtmRBCxaSp85PMkr",
"created_at": "2024-12-04T10:49:28.93Z",
"updated_at": "2024-12-04T10:49:51.38Z",
"3ds_redirect_url": null,
"additional_buyer_charges": null,
"additional_healthcare_data": null,
"additional_purchase_data": null,
"address_verification": "POSTAL_CODE_AND_STREET_MATCH",
"amount": 100,
"amount_requested": 100,
"application": "APc9vhYcPsRuTSpKD9KpMtPe",
"currency": "USD",
"expires_at": "2024-12-11T10:49:28.93Z",
"failure_code": null,
"failure_message": null,
"idempotency_id": null,
"is_void": false,
"merchant": "MU7noQ1wdgdAeAfymw2rfBMq",
"merchant_identity": "IDjvxGeXBLKH1V9YnWm1CS4n",
"messages": [],
"raw": null,
"receipt_last_printed_at": null,
"security_code_verification": "MATCHED",
"source": "PIkxmtueemLD6dN9ZoWGHT44",
"state": "SUCCEEDED",
"tags": {
"order_number": "21DFASJSAKAS"
},
"trace_id": "f9d829f3-6d84-41f1-a662-38ca99206af2",
"transfer": null,
"void_state": "PENDING",
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/authorizations/AUh4LG8REtmRBCxaSp85PMkr"
},
"application": {
"href": "https://api.ahrvo.network/payments/na/applications/APc9vhYcPsRuTSpKD9KpMtPe"
},
"merchant_identity": {
"href": "https://api.ahrvo.network/payments/na/identities/IDjvxGeXBLKH1V9YnWm1CS4n"
}
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| id | string | Authorization ID |
| created_at | string | Original authorization creation time |
| updated_at | string | Last update time (void request time) |
| amount | integer | Original authorized amount in cents |
| state | string | Authorization state (SUCCEEDED, CANCELED) |
| is_void | boolean | Whether void is complete (may be false initially) |
| void_state | string | Void operation state (PENDING, SUCCEEDED, FAILED) |
| merchant | string | Merchant ID |
| source | string | Payment instrument ID |
| transfer | string | Transfer ID (null if not captured) |
| tags | object | Metadata tags |
Void States
| State | Description |
|---|---|
| UNATTEMPTED | No void attempt made |
| PENDING | Void request submitted, awaiting confirmation |
| SUCCEEDED | Void completed successfully |
| FAILED | Void attempt failed |
Additional Information
-
Void Timeline: Processing time
- Void request is immediate
void_statestarts as PENDING- Changes to SUCCEEDED when confirmed
- Hold release time varies by issuer (up to 7 days)
- Check
is_voidfield for completion status
-
Cannot Void Captured: Only uncaptured authorizations
- Check
transferfield is null - Cannot void if already captured
- Use refund instead for captured transactions
- Void must happen before capture
- Check
-
Irreversible: Permanent cancellation
- Voided authorizations cannot be un-voided
- Cannot capture after void
- Must create new authorization if needed
- Void is final decision
-
Fund Release: Customer impact
- Funds held on customer's card
- Void releases the hold
- Customer sees available balance increase
- Timing depends on issuing bank
- Some banks release immediately, others take days
-
Use Cases: When to void
- Customer cancels order before fulfillment
- Inventory not available
- Pricing error discovered
- Order cannot be fulfilled
- Customer requests cancellation
Use Cases
Standard Void
// Void an authorization to release funds
async function voidAuthorization(authorizationId, reason = '') {
console.log('Voiding authorization...');
// Check authorization first
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
const auth = await authResponse.json();
if (auth.transfer) {
console.log('✗ Cannot void: Authorization already captured');
return null;
}
if (auth.is_void) {
console.log('✗ Authorization already voided');
return auth;
}
// Void the authorization
const response = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
);
const voided = await response.json();
console.log('✓ Void request submitted');
console.log(` Void State: ${voided.void_state}`);
console.log(` Amount Released: $${voided.amount / 100}`);
if (reason) {
console.log(` Reason: ${reason}`);
}
return voided;
}
Void with Status Check
// Void and monitor status until complete
async function voidAndWaitForCompletion(authorizationId) {
console.log('Initiating void...');
// Submit void request
const voidResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
);
let voided = await voidResponse.json();
console.log('✓ Void request submitted');
console.log(`Initial state: ${voided.void_state}`);
// Poll for completion
let attempts = 0;
const maxAttempts = 10;
while (voided.void_state === 'PENDING' && attempts < maxAttempts) {
console.log(`Checking void status (attempt ${attempts + 1})...`);
await new Promise(resolve => setTimeout(resolve, 2000));
const statusResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
voided = await statusResponse.json();
attempts++;
}
if (voided.void_state === 'SUCCEEDED') {
console.log('✓ Void completed successfully');
} else if (voided.void_state === 'FAILED') {
console.log('✗ Void failed');
} else {
console.log('⚠ Void still pending after polling');
}
return voided;
}
Order Cancellation Workflow
// Complete order cancellation with void
async function cancelOrder(orderId, authorizationId) {
console.log('=== Order Cancellation ===\n');
// Step 1: Verify order can be cancelled
console.log('Step 1: Verifying order status...');
// In real scenario, check order database
console.log('✓ Order eligible for cancellation');
// Step 2: Check authorization
console.log('\nStep 2: Checking authorization...');
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
const auth = await authResponse.json();
if (auth.transfer) {
console.log('✗ Payment already captured - refund required instead');
return { cancelled: false, requiresRefund: true };
}
if (auth.is_void) {
console.log('✓ Authorization already voided');
return { cancelled: true, alreadyVoided: true };
}
console.log('✓ Authorization can be voided');
// Step 3: Void authorization
console.log('\nStep 3: Voiding authorization...');
const voidResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
);
const voided = await voidResponse.json();
console.log('✓ Authorization voided');
// Step 4: Update order status
console.log('\nStep 4: Updating order status...');
// In real scenario, update database
console.log('✓ Order marked as cancelled');
// Step 5: Notify customer
console.log('\nStep 5: Notifying customer...');
console.log('✓ Cancellation email sent');
console.log('\n=== Cancellation Complete ===');
console.log(`Order ${orderId} cancelled successfully`);
console.log(`Authorization ${authorizationId} voided`);
console.log(`Hold of $${voided.amount / 100} will be released`);
return {
cancelled: true,
orderId,
authorizationId,
amount: voided.amount
};
}
Void After Failed Fulfillment
// Void when unable to fulfill order
async function voidAfterFulfillmentFailure(authorizationId, failureReason) {
console.log('Order fulfillment failed - voiding authorization');
console.log(`Reason: ${failureReason}`);
const response = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
);
const voided = await response.json();
// Log for audit trail
console.log('Void Details:');
console.log(` Authorization: ${authorizationId}`);
console.log(` Amount: $${voided.amount / 100}`);
console.log(` Failure Reason: ${failureReason}`);
console.log(` Voided At: ${voided.updated_at}`);
return {
voided: true,
authorizationId,
amount: voided.amount,
reason: failureReason,
voidedAt: voided.updated_at
};
}
// Example usage
await voidAfterFulfillmentFailure(
'AUh4LG8REtmRBCxaSp85PMkr',
'Product out of stock'
);
Batch Void Processing
// Void multiple authorizations in batch
async function batchVoidAuthorizations(authorizationIds, reason = '') {
console.log(`Voiding batch of ${authorizationIds.length} authorizations...\n`);
const results = {
successful: [],
failed: [],
alreadyVoided: [],
alreadyCaptured: []
};
for (const authId of authorizationIds) {
try {
// Check authorization first
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
const auth = await authResponse.json();
if (auth.transfer) {
results.alreadyCaptured.push(authId);
console.log(`⚠ ${authId} - Already captured, skipping`);
continue;
}
if (auth.is_void) {
results.alreadyVoided.push(authId);
console.log(`⚠ ${authId} - Already voided, skipping`);
continue;
}
// Void the authorization
const voidResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
);
const voided = await voidResponse.json();
results.successful.push({
authorizationId: authId,
amount: voided.amount,
voidState: voided.void_state
});
console.log(`✓ ${authId} - Voided`);
} catch (error) {
results.failed.push({
authorizationId: authId,
error: error.message
});
console.log(`✗ ${authId} - Failed: ${error.message}`);
}
// Rate limiting
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log(`\n=== Batch Void Complete ===`);
console.log(`Successful: ${results.successful.length}`);
console.log(`Failed: ${results.failed.length}`);
console.log(`Already Voided: ${results.alreadyVoided.length}`);
console.log(`Already Captured: ${results.alreadyCaptured.length}`);
return results;
}
Void with Comprehensive Validation
// Void with complete validation and error handling
async function safeVoid(authorizationId, reason = '') {
try {
console.log('Validating authorization for void...');
// Fetch current authorization state
const authResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
if (!authResponse.ok) {
throw new Error('Authorization not found');
}
const auth = await authResponse.json();
// Validation checks
if (auth.state !== 'SUCCEEDED') {
throw new Error(`Authorization state is ${auth.state}, cannot void`);
}
if (auth.transfer) {
throw new Error('Authorization has been captured and cannot be voided. Use refund instead.');
}
if (auth.is_void) {
console.log('✓ Authorization already voided');
return { success: true, alreadyVoided: true, authorization: auth };
}
if (auth.void_state === 'PENDING') {
console.log('✓ Void already in progress');
return { success: true, voidPending: true, authorization: auth };
}
// Perform void
console.log('Submitting void request...');
const voidResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
);
if (!voidResponse.ok) {
const error = await voidResponse.json();
throw new Error(`Void failed: ${error.message || 'Unknown error'}`);
}
const voided = await voidResponse.json();
console.log('✓ Void successful');
console.log(` Amount Released: $${voided.amount / 100}`);
console.log(` Void State: ${voided.void_state}`);
if (reason) {
console.log(` Reason: ${reason}`);
}
return {
success: true,
authorization: voided,
reason
};
} catch (error) {
console.error('✗ Void failed:', error.message);
return {
success: false,
error: error.message
};
}
}
Best Practices
-
Check Before Void: Verify authorization state
// Good - verify first
const auth = await fetchAuthorization(authId);
if (!auth.transfer && !auth.is_void) {
await voidAuthorization(authId);
}
// Bad - void blindly
await voidAuthorization(authId); // May fail -
Void Promptly: Don't delay unnecessarily
- Void as soon as you know order won't fulfill
- Faster fund release for customers
- Better customer experience
- Reduces complaints
-
Document Reason: Track why voided
await voidAuthorization(authId, 'Customer requested cancellation'); -
Notify Customer: Communicate void
- Send cancellation confirmation
- Explain hold release timing (up to 7 days)
- Provide customer service contact
- Set proper expectations
-
Handle Timing: Bank processing varies
- Void state may be PENDING initially
- Poll for SUCCEEDED if needed
- Inform customers of 1-7 day timeline
- Some banks release immediately
-
Monitor Void State: Check completion
const auth = await fetchAuthorization(authId);
if (auth.void_state === 'SUCCEEDED') {
console.log('Void complete');
} else if (auth.void_state === 'PENDING') {
console.log('Void processing...');
}
Common Workflows
Expired Item Void
// Void authorizations for items no longer available
async function voidExpiredItems(orderItems, authorizationId) {
const unavailableItems = orderItems.filter(item => !item.inStock);
if (unavailableItems.length === 0) {
console.log('All items available, no void needed');
return null;
}
console.log(`${unavailableItems.length} items unavailable`);
console.log('Voiding authorization...');
const voided = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${authorizationId}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
).then(r => r.json());
console.log('✓ Authorization voided');
console.log('Customer will be notified of unavailable items');
return {
voided: true,
unavailableItems,
amountReleased: voided.amount
};
}
Automatic Expiry Void
// Auto-void authorizations approaching expiration
async function autoVoidExpiringAuthorizations() {
console.log('Checking for expiring authorizations...\n');
// Get uncaptured authorizations
const response = await fetch(
'https://api.ahrvo.network/payments/na/authorizations?state=SUCCEEDED&limit=100',
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
const data = await response.json();
const now = new Date();
const oneDayFromNow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const expiringSoon = [];
for (const auth of data._embedded.authorizations) {
// Fetch full details
const detailResponse = await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${auth.id}`,
{
headers: {
'Authorization': 'Basic ' + btoa('username:password')
}
}
);
const detail = await detailResponse.json();
// Check if expiring soon and not captured/voided
const expiresAt = new Date(detail.expires_at);
if (expiresAt <= oneDayFromNow && !detail.transfer && !detail.is_void) {
expiringSoon.push(detail);
}
}
console.log(`Found ${expiringSoon.length} authorizations expiring within 24 hours`);
// Void expiring authorizations
for (const auth of expiringSoon) {
console.log(`Voiding ${auth.id} (expires ${auth.expires_at})...`);
await fetch(
`https://api.ahrvo.network/payments/na/authorizations/${auth.id}`,
{
method: 'PUT',
headers: {
'Authorization': 'Basic ' + btoa('username:password'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
void_me: true
})
}
);
console.log('✓ Voided');
}
console.log(`\n✓ Auto-void complete - ${expiringSoon.length} authorizations voided`);
return expiringSoon;
}
Security Considerations
-
Verify Ownership: Ensure authorization belongs to merchant
- Check merchant ID matches
- Validate permissions
- Don't void other merchants' authorizations
-
Audit Trail: Log all voids
- Record who voided
- Record when voided
- Record reason for void
- Track customer impact
-
Authorization: Proper permissions required
- Only authorized users can void
- Consider approval workflows for large amounts
- Monitor void activity
Error Responses
Already Captured
{
"status": 422,
"message": "Authorization has been captured and cannot be voided"
}
Already Voided
{
"status": 422,
"message": "Authorization has already been voided"
}
Authorization Not Found
{
"status": 404,
"message": "Authorization not found"
}
Invalid State
{
"status": 422,
"message": "Authorization state does not allow void"
}
Troubleshooting
Void Fails - Already Captured
- Check
transferfield before void - Authorization was already captured
- Use refund instead of void
- Cannot undo capture
Void Fails - Already Voided
- Check
is_voidfield - Check
void_statefield - Authorization already voided
- No action needed
Void Stuck in PENDING
- Void submitted but not confirmed
- May take time to process
- Poll void_state for updates
- Contact support if stuck >24 hours
Hold Not Released
- Void successful but customer still sees hold
- Issuing bank controls timing
- Can take up to 7 days
- Customer should contact their bank
Related Endpoints
- POST /authorizations: Create authorization
- GET /authorizations/{id}: Check authorization status
- PUT /authorizations/{id} (capture): Capture instead of void
- POST /transfers/{id}/reversals: Refund captured authorization