Skip to main content

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

ParameterTypeRequiredDescription
payment_instrument_idstringYesPayment Instrument ID (in URL path)
limitintegerNoNumber 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 raw field for reason
  • 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
  • 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 raw field
    • Map error codes to friendly messages
    • "Card expired" not "EXPIRED_CARD"
    • Suggest next steps
  • 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.succeeded
    • payment_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

  1. Customer enters card details
  2. Create Payment Instrument
  3. Platform automatically creates Verification
  4. Wait 3-5 seconds (or use webhook)
  5. List Payment Instrument Verifications
  6. Check latest verification state
  7. If SUCCEEDED: Card ready
  8. If FAILED: Show error, ask for different card
  9. 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

  1. Customer enters bank details
  2. Create Payment Instrument (bank account)
  3. Platform initiates verification
  4. For micro-deposits: Inform customer of 1-2 day wait
  5. Listen for webhook: payment_instrument.verification.succeeded
  6. Or: Poll daily for verification status
  7. Once SUCCEEDED: Bank account ready
  8. 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
  • 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