List Payment Instrument Verifications
Overview
Retrieve all Verifications performed for a specific Payment Instrument (card or bank account). Verifications validate that the payment method is active and owned by the account holder.
Resource Access
- User Permissions: Admin users only
- Endpoint:
GET /payment_instruments/\{payment_instrument_id}/verifications
Arguments
| Parameter | Type | Required | Description |
|---|---|---|---|
| payment_instrument_id | string | Yes | Payment Instrument ID (in URL path) |
| limit | integer | No | Number of results per page (default: 10, max: 100) |
Example Request
curl -X GET \
'https://api.ahrvo.network/payments/na/payment_instruments/PIcreditCard456/verifications?limit=20' \
-u username:password
Example Response (Card Verification)
{
"_embedded": {
"verifications": [
{
"id": "VIverification456",
"created_at": "2023-12-10T20:00:00Z",
"updated_at": "2023-12-10T20:00:03Z",
"state": "SUCCEEDED",
"merchant": "MUmerchant123",
"payment_instrument": "PIcreditCard456",
"processor": "DUMMY_V1",
"raw": null,
"trace_id": "VT_12345678",
"type": "PAYMENT_INSTRUMENT",
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/verifications/VIverification456"
},
"payment_instrument": {
"href": "https://api.ahrvo.network/payments/na/payment_instruments/PIcreditCard456"
},
"merchant": {
"href": "https://api.ahrvo.network/payments/na/merchants/MUmerchant123"
}
}
}
]
},
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/payment_instruments/PIcreditCard456/verifications?limit=20"
}
},
"page": {
"limit": 20,
"offset": 0,
"count": 1
}
}
Example Response (Failed Card Verification)
{
"_embedded": {
"verifications": [
{
"id": "VIverification457",
"created_at": "2023-12-10T19:30:00Z",
"updated_at": "2023-12-10T19:30:05Z",
"state": "FAILED",
"merchant": "MUmerchant123",
"payment_instrument": "PIcreditCard789",
"processor": "DUMMY_V1",
"raw": "{\"error\": \"Card expired\", \"code\": \"EXPIRED_CARD\"}",
"trace_id": "VT_12345679",
"type": "PAYMENT_INSTRUMENT",
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/verifications/VIverification457"
},
"payment_instrument": {
"href": "https://api.ahrvo.network/payments/na/payment_instruments/PIcreditCard789"
},
"merchant": {
"href": "https://api.ahrvo.network/payments/na/merchants/MUmerchant123"
}
}
}
]
},
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/payment_instruments/PIcreditCard789/verifications"
}
},
"page": {
"limit": 10,
"offset": 0,
"count": 1
}
}
Example Response (Bank Account Verification)
{
"_embedded": {
"verifications": [
{
"id": "VIverification458",
"created_at": "2023-12-10T18:00:00Z",
"updated_at": "2023-12-10T18:00:10Z",
"state": "SUCCEEDED",
"merchant": "MUmerchant123",
"payment_instrument": "PIbankAccount123",
"processor": "DUMMY_V1",
"raw": null,
"trace_id": "VT_12345680",
"type": "PAYMENT_INSTRUMENT",
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/verifications/VIverification458"
},
"payment_instrument": {
"href": "https://api.ahrvo.network/payments/na/payment_instruments/PIbankAccount123"
},
"merchant": {
"href": "https://api.ahrvo.network/payments/na/merchants/MUmerchant123"
}
}
}
]
},
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/payment_instruments/PIbankAccount123/verifications"
}
},
"page": {
"limit": 10,
"offset": 0,
"count": 1
}
}
Additional Information
- Verification Type: Always "PAYMENT_INSTRUMENT"
- Validates card/bank account
- Checks if active
- Verifies ownership
- Confirms funds availability (bank accounts)
- States:
- PENDING: Verification in progress
- Submitted to processor
- Awaiting response
- Typically seconds
- Rare (usually fast)
- SUCCEEDED: Payment method verified
- Card/account is valid
- Active and not expired
- Can be used for payments
- Ready to charge
- FAILED: Verification failed
- Card expired
- Invalid account number
- Insufficient funds (bank)
- Card declined
- Check
rawfield for reason
- PENDING: Verification in progress
- Automatic Creation: Created automatically
- When Payment Instrument is created
- When card/bank account is tokenized
- Platform triggers verification
- No manual creation needed
- Verification Methods:
- Cards:
- $0 authorization (may show as pending)
- Immediately reversed
- Validates card is active
- Checks expiration
- Bank Accounts:
- Micro-deposits (2 small deposits)
- Or instant verification via Plaid/similar
- Account ownership confirmation
- Routing number validation
- Cards:
- Merchant: Associated merchant
- Who created the payment instrument
- Links verification to merchant account
- For reporting and tracking
- Processor: Verification provider
- DUMMY_V1: Test/sandbox
- Production processors vary
- Determines verification method
- Different rules per processor
- Raw: Processor response
- Usually null for SUCCEEDED
- Contains error details for FAILED
- JSON string format
- Codes like EXPIRED_CARD, INVALID_NUMBER
- Use for debugging
- Trace ID: Processor reference
- External verification ID
- Use for support tickets
- Links to processor logs
- Helps troubleshoot
- Multiple Verifications: Possible
- Re-verification on updates
- Manual re-verification
- Historical record
- Audit trail
- Chronological Order: Newest first
- Most recent verification at top
- Shows verification history
- Latest is current status
- Cannot change sort
Use Cases
Check Card Validity
# After customer enters card, verify it's valid
curl -X GET \
'https://api.ahrvo.network/payments/na/payment_instruments/PIcreditCard456/verifications' \
-u username:password
# Check state of latest verification
# SUCCEEDED = card is valid, ready to charge
# FAILED = card declined, ask for different card
Debug Failed Payment Setup
// Customer can't add payment method, find out why
async function debugPaymentInstrumentVerification(paymentInstrumentId) {
const response = await listPaymentInstrumentVerifications(paymentInstrumentId);
const verifications = response._embedded.verifications;
if (verifications.length === 0) {
console.log('No verifications - payment instrument may not be submitted yet');
return;
}
const latest = verifications[0]; // Most recent
console.log('Verification Status:', latest.state);
console.log('Created:', latest.created_at);
console.log('Type:', latest.type);
if (latest.state === 'FAILED') {
console.error('Verification Failed!');
console.log('Raw error:', latest.raw);
// Parse error
if (latest.raw) {
const error = JSON.parse(latest.raw);
console.log('Error code:', error.code);
console.log('Error message:', error.error);
// Display user-friendly message
switch (error.code) {
case 'EXPIRED_CARD':
return 'Card has expired. Please use a different card.';
case 'INVALID_NUMBER':
return 'Invalid card number. Please check and try again.';
case 'INSUFFICIENT_FUNDS':
return 'Insufficient funds. Please use a different payment method.';
default:
return 'Card declined. Please try a different card.';
}
}
} else if (latest.state === 'SUCCEEDED') {
console.log('Payment instrument verified successfully');
return 'Ready to process payments';
} else {
console.log('Verification pending...');
return 'Processing, please wait...';
}
}
Bank Account Verification Status
// Check bank account verification status
async function checkBankAccountVerification(paymentInstrumentId) {
const response = await listPaymentInstrumentVerifications(paymentInstrumentId);
const verifications = response._embedded.verifications;
if (verifications.length === 0) {
return {
status: 'not_started',
message: 'Bank account not yet verified'
};
}
const latest = verifications[0];
switch (latest.state) {
case 'SUCCEEDED':
return {
status: 'verified',
message: 'Bank account verified and ready to use',
verifiedAt: latest.updated_at
};
case 'FAILED':
const error = latest.raw ? JSON.parse(latest.raw) : {};
return {
status: 'failed',
message: 'Bank account verification failed',
reason: error.error || 'Unknown error',
code: error.code
};
case 'PENDING':
return {
status: 'pending',
message: 'Bank account verification in progress',
startedAt: latest.created_at
};
}
}
Card Verification History
// View all verification attempts for a card
async function auditCardVerifications(paymentInstrumentId) {
const response = await listPaymentInstrumentVerifications(
paymentInstrumentId,
{ limit: 100 }
);
const verifications = response._embedded.verifications;
console.log(`Total verifications: ${verifications.length}`);
verifications.forEach((v, index) => {
console.log(`\nAttempt ${index + 1}:`);
console.log(` Date: ${v.created_at}`);
console.log(` State: ${v.state}`);
console.log(` Processor: ${v.processor}`);
if (v.state === 'FAILED' && v.raw) {
const error = JSON.parse(v.raw);
console.log(` Error: ${error.code} - ${error.error}`);
}
});
// Check if latest is successful
const latest = verifications[0];
console.log(`\nCurrent status: ${latest.state}`);
return latest.state === 'SUCCEEDED';
}
Validate Before Charging
// Before creating transfer, verify card is valid
async function validateCardBeforeCharge(paymentInstrumentId) {
const response = await listPaymentInstrumentVerifications(paymentInstrumentId);
const verifications = response._embedded.verifications;
if (verifications.length === 0) {
throw new Error('Payment method not verified');
}
const latest = verifications[0];
if (latest.state !== 'SUCCEEDED') {
throw new Error(`Payment method verification ${latest.state.toLowerCase()}`);
}
// Check how recent the verification is
const verifiedAt = new Date(latest.updated_at);
const ageInDays = (new Date() - verifiedAt) / (1000 * 60 * 60 * 24);
if (ageInDays > 30) {
console.warn('Verification is over 30 days old - may want to re-verify');
}
console.log('Payment method verified and ready to charge');
return true;
}
Failed Verification Analysis
// Analyze failed verifications to identify patterns
async function analyzeFailedVerifications(paymentInstrumentIds) {
const failureCodes = {};
let totalFailed = 0;
for (const id of paymentInstrumentIds) {
const response = await listPaymentInstrumentVerifications(id);
const verifications = response._embedded.verifications;
verifications.forEach(v => {
if (v.state === 'FAILED' && v.raw) {
totalFailed++;
const error = JSON.parse(v.raw);
const code = error.code || 'UNKNOWN';
failureCodes[code] = (failureCodes[code] || 0) + 1;
}
});
}
console.log(`Total failed verifications: ${totalFailed}`);
console.log('Failure breakdown:');
Object.entries(failureCodes)
.sort((a, b) => b[1] - a[1])
.forEach(([code, count]) => {
const percentage = (count / totalFailed * 100).toFixed(1);
console.log(` ${code}: ${count} (${percentage}%)`);
});
return failureCodes;
}
Best Practices
- Check After Creation: Verify payment instrument
- Create Payment Instrument
- Wait 3-5 seconds
- List verifications
- Check latest state
- Handle FAILED appropriately
- User-Friendly Errors: Don't show raw errors
- Parse
rawfield - Map error codes to friendly messages
- "Card expired" not "EXPIRED_CARD"
- Suggest next steps
- Parse
- Poll for PENDING: If verification pending
- Wait 5 seconds
- Check again
- Max 2-3 retries
- Then show error
- Better: use webhooks
- Webhooks: Listen for events
payment_instrument.verification.succeededpayment_instrument.verification.failed- Real-time notification
- No polling needed
- Validate Before Charging: Always check
- List verifications before Transfer
- Verify state is SUCCEEDED
- Handle FAILED gracefully
- Prevents declined charges
- Bank Account Timing: Micro-deposits take time
- 1-2 business days
- Don't expect instant SUCCEEDED
- Inform customer about timing
- Send notification when verified
- Re-verification: For old cards
- If verification > 30 days old
- Consider re-verifying
- Cards may expire
- Accounts may close
Common Workflows
Card Setup Flow
- Customer enters card details
- Create Payment Instrument
- Platform automatically creates Verification
- Wait 3-5 seconds (or use webhook)
- List Payment Instrument Verifications
- Check latest verification state
- If SUCCEEDED: Card ready
- If FAILED: Show error, ask for different card
- If PENDING: Show loading, poll again
Failed Card Handling
async function handleFailedCard(paymentInstrumentId) {
const response = await listPaymentInstrumentVerifications(paymentInstrumentId);
const latest = response._embedded.verifications[0];
if (latest.state !== 'FAILED') {
return;
}
// Parse error
const error = latest.raw ? JSON.parse(latest.raw) : {};
const errorCode = error.code;
// Display appropriate message
let userMessage = '';
let canRetry = false;
switch (errorCode) {
case 'EXPIRED_CARD':
userMessage = 'This card has expired. Please use a different card.';
canRetry = false;
break;
case 'INVALID_NUMBER':
userMessage = 'Invalid card number. Please check your card details.';
canRetry = true;
break;
case 'INSUFFICIENT_FUNDS':
userMessage = 'Insufficient funds. Please use a different payment method.';
canRetry = false;
break;
case 'CARD_DECLINED':
userMessage = 'Card declined by your bank. Please try a different card.';
canRetry = false;
break;
default:
userMessage = 'Unable to verify card. Please try a different payment method.';
canRetry = false;
}
return {
message: userMessage,
canRetry,
shouldRequestNewCard: !canRetry
};
}
Bank Account Verification Flow
- Customer enters bank details
- Create Payment Instrument (bank account)
- Platform initiates verification
- For micro-deposits: Inform customer of 1-2 day wait
- Listen for webhook:
payment_instrument.verification.succeeded - Or: Poll daily for verification status
- Once SUCCEEDED: Bank account ready
- If FAILED: Ask customer to verify details
Pre-charge Validation
// Before creating transfer, validate payment method
async function preChargeValidation(paymentInstrumentId, amount) {
// Check verification
const response = await listPaymentInstrumentVerifications(paymentInstrumentId);
if (response._embedded.verifications.length === 0) {
throw new Error('Payment method not verified');
}
const latest = response._embedded.verifications[0];
if (latest.state === 'PENDING') {
throw new Error('Payment method verification in progress. Please try again in a moment.');
}
if (latest.state === 'FAILED') {
const error = latest.raw ? JSON.parse(latest.raw) : {};
throw new Error(`Payment method declined: ${error.error || 'Unknown error'}`);
}
// Verification succeeded, proceed with charge
console.log('Payment method validated, proceeding with charge');
const transfer = await createTransfer({
amount,
source: paymentInstrumentId,
// ... other params
});
return transfer;
}
Troubleshooting
No Verifications Returned
- Payment Instrument may not be fully created
- Check Payment Instrument state
- Verifications created automatically
- Contact support if missing
Verification Stuck in PENDING
- Usually completes in seconds
- Cards: Max 30 seconds
- Bank accounts: Can take 1-2 days
- If card verification > 1 minute, retry
- Provide trace_id to support
SUCCEEDED but Charge Failed
- Verification success != guaranteed charge
- Card had funds at verification
- May decline later (insufficient funds)
- Verification checks validity, not balance
- Always handle charge failures
Multiple FAILED Attempts
- Customer may be entering wrong info
- Ask customer to verify card details
- Try different card
- Contact card issuer
- May indicate fraud prevention
Related Endpoints
- GET /payment_instruments/{id}: View payment instrument details
- POST /payment_instruments: Create payment instrument (triggers verification)
- GET /verifications/{id}: Fetch specific verification
- GET /merchants/{id}/verifications: List merchant verifications