Skip to main content

Payment Service Provider Integration

Payment service provider integration connects your cash and voucher assistance platform to the financial infrastructure that delivers transfers to beneficiaries. This task covers the technical integration process from initial API configuration through production deployment, enabling your CVA platform to submit payment instructions and receive transaction status updates. Complete this task after selecting your PSP and CVA platform, and before processing live transfers.

Prerequisites

Gather the following before beginning integration work. Missing prerequisites cause the majority of integration delays.

Contractual and administrative

RequirementDetailHow to verify
PSP contract executedSigned agreement covering transaction fees, settlement terms, liabilityContract copy with authorised signatures
Merchant account activePSP has provisioned your organisation’s merchant accountPSP confirmation email with merchant ID
API access approvedPSP has enabled API access for your merchant accountAPI credentials issued (not necessarily activated)
Compliance documentation submittedKYC/AML documentation accepted by PSPPSP compliance approval confirmation
Test environment provisionedSandbox or UAT environment availableTest environment URL and credentials

Technical access

You need the following credentials and access levels:

PSP credentials:

  • API key or client ID for authentication
  • API secret or client secret (store securely, never in code repositories)
  • Merchant ID or account identifier
  • Webhook signing secret (if PSP uses webhook signatures)

CVA platform access:

  • Administrator access to CVA platform
  • Permission to configure external integrations
  • Access to platform API settings or integration module

Infrastructure access:

  • Ability to configure outbound firewall rules (if applicable)
  • DNS management access (for webhook endpoints)
  • SSL certificate management (for webhook HTTPS endpoints)

Technical environment

Your environment must meet these requirements:

CVA Platform Requirements:
- API integration module enabled
- Webhook endpoint capability (HTTPS with valid certificate)
- Secure credential storage (environment variables or secrets manager)
Network Requirements:
- Outbound HTTPS (TCP 443) to PSP API endpoints
- Inbound HTTPS (TCP 443) for webhook callbacks
- Static IP or IP range (if PSP requires IP allowlisting)
Development Tools:
- API testing tool (Postman, Insomnia, or curl)
- JSON parser for response inspection
- Log access for debugging

Verify your CVA platform version supports the PSP you are integrating. Check the platform’s integration documentation for supported PSPs and minimum version requirements.

Integration architecture

Before beginning configuration, understand the data flow between systems:

+-------------------------------------------------------------------+
| CVA PLATFORM |
| |
| +------------------+ +------------------+ |
| | Beneficiary | | Payment | |
| | Registry +---->| Batch | |
| | (ID, account) | | Generator | |
| +------------------+ +--------+---------+ |
| | |
| +--------v---------+ |
| | PSP Integration | |
| | Module | |
| +--------+---------+ |
| | |
+------------------------------------+------------------------------+
|
(1) Payment | (4) Status
Request | Update
v
+-------------------------------------------------------------------+
| PSP API GATEWAY |
| |
| +------------------+ +------------------+ |
| | Authentication | | Rate Limiting | |
| | (API key/OAuth) | | (per merchant) | |
| +------------------+ +------------------+ |
| |
+-------------------------------------------------------------------+
|
(2) Process | (3) Delivery
Payment | Confirmation
v
+-------------------------------------------------------------------+
| PAYMENT RAILS |
| |
| +------------+ +------------+ +------------+ +------------+ |
| | Mobile | | Bank | | Agent | | Card | |
| | Money | | Transfer | | Network | | Network | |
| +------------+ +------------+ +------------+ +------------+ |
| |
+-------------------------------------------------------------------+
|
v
+---------+----------+
| BENEFICIARY |
| (receives funds) |
+--------------------+

The integration handles four primary flows: payment submission (1), payment processing (2), delivery confirmation from payment rails (3), and status updates back to your platform (4). Your configuration must address each flow.

Procedure

Phase 1: Test environment configuration

  1. Configure PSP API credentials in your test environment. Store credentials in environment variables or your platform’s secrets manager, never in configuration files committed to version control.

    For environment variable configuration:

Terminal window
# Set in deployment environment, not in code
export PSP_API_KEY="test_key_abc123def456"
export PSP_API_SECRET="test_secret_xyz789"
export PSP_MERCHANT_ID="MERCHANT_TEST_001"
export PSP_API_URL="https://sandbox.psp-example.com/api/v2"

For CVA platforms with built-in credential storage, navigate to Settings > Integrations > Payment Providers and enter credentials in the designated fields. Enable the “Test Mode” or “Sandbox” toggle.

  1. Configure the API endpoint URL for the test environment. PSPs provide separate URLs for sandbox and production:

    EnvironmentTypical URL pattern
    Sandboxhttps://sandbox.provider.com/api/v2
    UAThttps://uat.provider.com/api/v2
    Productionhttps://api.provider.com/api/v2

    Enter the sandbox URL in your CVA platform’s PSP configuration. Verify the URL responds:

Terminal window
curl -I https://sandbox.psp-example.com/api/v2/health
# Expected: HTTP/2 200
  1. Test authentication by requesting an access token or making an authenticated test call. The method depends on your PSP’s authentication scheme.

    For API key authentication:

Terminal window
curl -X GET "https://sandbox.psp-example.com/api/v2/account/balance" \
-H "Authorization: Bearer test_key_abc123def456" \
-H "X-Merchant-ID: MERCHANT_TEST_001"

For OAuth 2.0 client credentials:

Terminal window
curl -X POST "https://sandbox.psp-example.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=test_key_abc123def456" \
-d "client_secret=test_secret_xyz789"

Expected response (OAuth):

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}

Store the token expiry time and implement token refresh before expiry. Most OAuth tokens expire in 1 hour (3600 seconds).

  1. Configure field mapping between your CVA platform and the PSP API. Your platform stores beneficiary data in its schema; the PSP expects data in its schema. Map fields explicitly:
CVA Platform Field PSP API Field
--------------------------------------------------
beneficiary.id recipient.external_id
beneficiary.full_name recipient.name
beneficiary.phone_number recipient.mobile_number
beneficiary.account_number recipient.account
transfer.amount transaction.amount
transfer.currency transaction.currency
transfer.reference transaction.reference
programme.id metadata.programme_id

Most CVA platforms provide a field mapping interface. Configure each required field. Mark mandatory PSP fields that lack CVA platform equivalents and determine how to populate them (static values, derived values, or platform customisation).

  1. Configure webhook endpoint for status callbacks. The PSP sends transaction status updates to a URL you specify. Create or identify the webhook endpoint in your CVA platform:
Webhook URL: https://cva.example.org/api/webhooks/psp-status
Requirements:
- HTTPS with valid SSL certificate (not self-signed)
- Responds within 5 seconds (PSP timeout threshold)
- Returns HTTP 200 for successful receipt
- Publicly accessible (not behind VPN)

Register the webhook URL with the PSP:

Terminal window
curl -X POST "https://sandbox.psp-example.com/api/v2/webhooks" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://cva.example.org/api/webhooks/psp-status",
"events": ["payment.completed", "payment.failed", "payment.reversed"],
"secret": "webhook_signing_secret_abc123"
}'

The PSP returns a webhook ID. Store this for future management:

{
"id": "whk_abc123",
"url": "https://cva.example.org/api/webhooks/psp-status",
"status": "active",
"events": ["payment.completed", "payment.failed", "payment.reversed"]
}
  1. Configure webhook signature verification. PSPs sign webhook payloads to prevent spoofing. Implement verification in your webhook handler:
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
"""Verify PSP webhook signature using HMAC-SHA256."""
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In webhook handler:
signature = request.headers.get('X-PSP-Signature')
if not verify_webhook_signature(request.body, signature, WEBHOOK_SECRET):
return Response(status=401) # Reject unsigned/invalid webhooks

Never process webhooks without signature verification in production.

Phase 2: Test transactions

  1. Create test beneficiaries in your CVA platform using PSP-provided test credentials. PSPs provide test phone numbers and account numbers that simulate successful and failed transactions:

    Test identifierExpected behaviour
    +255700000001Success, instant
    +255700000002Success, delayed (30 seconds)
    +255700000003Failure, insufficient balance
    +255700000004Failure, invalid account
    +255700000005Failure, timeout

    Create beneficiary records using these test identifiers. Ensure each test scenario has a corresponding beneficiary.

  2. Submit a single test payment to verify end-to-end flow. Select one test beneficiary and create a transfer:

Terminal window
curl -X POST "https://sandbox.psp-example.com/api/v2/payments" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: test-payment-001" \
-d '{
"recipient": {
"external_id": "BEN-001",
"name": "Test Beneficiary One",
"mobile_number": "+255700000001"
},
"transaction": {
"amount": 50000,
"currency": "TZS",
"reference": "CVA-2024-001-001"
},
"metadata": {
"programme_id": "PROG-001",
"distribution_id": "DIST-001"
}
}'

Expected response:

{
"id": "pay_abc123",
"status": "pending",
"recipient": {
"external_id": "BEN-001",
"mobile_number": "+255700000001"
},
"transaction": {
"amount": 50000,
"currency": "TZS",
"reference": "CVA-2024-001-001"
},
"created_at": "2024-11-16T10:30:00Z"
}

Note the payment ID (pay_abc123) for status tracking.

  1. Verify webhook receipt. Within 30 seconds of submission, your webhook endpoint should receive a status callback:
{
"event": "payment.completed",
"payment_id": "pay_abc123",
"status": "completed",
"completed_at": "2024-11-16T10:30:15Z",
"receipt": {
"provider_reference": "MPESA-ABC123XYZ",
"channel": "mobile_money"
}
}

Check your webhook logs to confirm receipt. If no webhook arrives within 60 seconds, verify webhook configuration and check PSP webhook delivery logs (most PSPs provide a webhook history view).

  1. Verify status synchronisation. Confirm your CVA platform updated the transfer status based on the webhook:
-- Check transfer status in CVA platform database
SELECT transfer_id, status, psp_reference, updated_at
FROM transfers
WHERE psp_payment_id = 'pay_abc123';
-- Expected result:
-- transfer_id | status | psp_reference | updated_at
-- TRF-001 | completed | MPESA-ABC123XYZ | 2024-11-16 10:30:16

If using the CVA platform interface, navigate to the transfer record and verify status shows “Completed” with the PSP reference number.

  1. Test failure scenarios using PSP test credentials that simulate failures. Submit payments to test accounts configured for failure:
Terminal window
# Test insufficient balance failure
curl -X POST "https://sandbox.psp-example.com/api/v2/payments" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: test-payment-fail-001" \
-d '{
"recipient": {
"external_id": "BEN-003",
"mobile_number": "+255700000003"
},
"transaction": {
"amount": 50000,
"currency": "TZS",
"reference": "CVA-2024-001-003"
}
}'

Expected webhook:

{
"event": "payment.failed",
"payment_id": "pay_def456",
"status": "failed",
"failure_reason": "insufficient_balance",
"failure_message": "Provider account has insufficient balance"
}

Verify your CVA platform correctly records the failure status and reason.

  1. Test batch payment submission. Most CVA distributions involve multiple payments submitted together. Create a batch of 10 test payments covering success and failure scenarios:
Terminal window
curl -X POST "https://sandbox.psp-example.com/api/v2/batches" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: test-batch-001" \
-d '{
"batch_reference": "DIST-2024-001",
"payments": [
{
"external_id": "BEN-001",
"mobile_number": "+255700000001",
"amount": 50000,
"currency": "TZS"
},
{
"external_id": "BEN-002",
"mobile_number": "+255700000002",
"amount": 50000,
"currency": "TZS"
}
// ... additional payments
]
}'

Expected response:

{
"batch_id": "bat_xyz789",
"status": "processing",
"total_payments": 10,
"total_amount": 500000,
"currency": "TZS"
}

Monitor batch completion through webhooks or polling the batch status endpoint.

Phase 3: Production configuration

  1. Complete PSP production onboarding. Request production API credentials from your PSP account manager. Production credentials are separate from test credentials and require additional verification:

    • Confirm bank account for settlement
    • Complete any outstanding compliance documentation
    • Sign production environment terms (if separate from main contract)
    • Request IP allowlisting for your production servers (if required)

    Production credential issuance takes 1 to 5 business days depending on the PSP.

  2. Configure production credentials in your CVA platform. Use a separate configuration profile for production, distinct from test configuration:

Terminal window
# Production environment variables
export PSP_API_KEY="prod_key_secure123"
export PSP_API_SECRET="prod_secret_secure456"
export PSP_MERCHANT_ID="MERCHANT_PROD_001"
export PSP_API_URL="https://api.psp-example.com/api/v2"
export PSP_ENVIRONMENT="production"

In platforms with built-in credential storage, disable “Test Mode” or “Sandbox” toggle before entering production credentials.

Credential security

Production API credentials must never appear in code repositories, logs, or error messages. Use secrets management systems and audit credential access. Rotate credentials immediately if exposure is suspected.

  1. Configure production webhook endpoint. Create a separate webhook registration for production, or update your existing registration to use production URLs:
Terminal window
curl -X POST "https://api.psp-example.com/api/v2/webhooks" \
-H "Authorization: Bearer $PROD_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://cva.example.org/api/webhooks/psp-status",
"events": ["payment.completed", "payment.failed", "payment.reversed"],
"secret": "prod_webhook_secret_secure789"
}'

Use a different webhook signing secret for production than for test environments.

  1. Configure transaction limits and controls. Set limits in your CVA platform to match PSP and programme requirements:

    Limit typeConfigurationExample value
    Single transaction maximumPSP configuration1,000,000 TZS
    Daily volume limitCVA platform50,000,000 TZS
    Batch size maximumPSP configuration500 payments
    Retry attemptsCVA platform3 attempts
    Retry delayCVA platform300 seconds

    Configure alerts when approaching limits (80% threshold recommended).

  2. Configure error handling and retry logic. Define how your integration handles transient failures:

Retry Policy Configuration:
Retryable errors:
- HTTP 429 (rate limited): Retry after Retry-After header value
- HTTP 500-503 (server error): Retry with exponential backoff
- Timeout: Retry with exponential backoff
- Network error: Retry with exponential backoff
Non-retryable errors:
- HTTP 400 (bad request): Log and flag for review
- HTTP 401 (unauthorized): Alert, do not retry
- HTTP 404 (not found): Log and flag for review
- Business logic failures: Log, do not retry automatically
Exponential backoff schedule:
- Attempt 1: Immediate
- Attempt 2: 30 seconds
- Attempt 3: 120 seconds
- Attempt 4: 480 seconds (8 minutes)
- Maximum attempts: 4

Configure these settings in your CVA platform’s integration module or implement in custom integration code.

  1. Verify idempotency handling. PSP integrations must handle duplicate submissions safely. Test that resubmitting a payment with the same idempotency key returns the original result without creating a duplicate:
Terminal window
# First submission
curl -X POST "https://api.psp-example.com/api/v2/payments" \
-H "Authorization: Bearer $PROD_ACCESS_TOKEN" \
-H "Idempotency-Key: unique-key-12345" \
-d '{"recipient": {...}, "transaction": {...}}'
# Returns: {"id": "pay_new123", "status": "pending"}
# Duplicate submission with same idempotency key
curl -X POST "https://api.psp-example.com/api/v2/payments" \
-H "Authorization: Bearer $PROD_ACCESS_TOKEN" \
-H "Idempotency-Key: unique-key-12345" \
-d '{"recipient": {...}, "transaction": {...}}'
# Returns: {"id": "pay_new123", "status": "pending"} # Same payment, not duplicate

Implement idempotency key generation in your CVA platform using a combination of transfer ID and submission timestamp.

Phase 4: Go-live

  1. Complete pre-go-live checklist. Verify all items before processing live payments:

    CategoryItemVerified
    CredentialsProduction API credentials configured
    CredentialsWebhook signing secret configured
    CredentialsCredentials stored in secrets manager
    NetworkOutbound connectivity to production API
    NetworkWebhook endpoint accessible from PSP
    NetworkSSL certificate valid and not expiring within 30 days
    ConfigurationField mapping verified
    ConfigurationTransaction limits configured
    ConfigurationRetry policy configured
    TestingSingle payment success tested
    TestingBatch payment tested
    TestingFailure handling tested
    TestingWebhook receipt verified
    MonitoringTransaction alerts configured
    MonitoringError rate alerts configured
    DocumentationIntegration documented
    DocumentationRunbook created for common issues
  2. Process a small live batch. Before full distribution, process a limited batch of 5 to 10 real payments to verify production behaviour:

    • Select beneficiaries with confirmed contact details
    • Use minimum viable transfer amount (per programme rules)
    • Monitor each payment through completion
    • Verify webhook receipt for each payment
    • Confirm beneficiary receipt (phone call or field verification)

    Document any discrepancies between test and production behaviour.

  3. Monitor the initial distribution closely. For the first full distribution, assign staff to monitor:

    • Payment submission success rate
    • Webhook receipt rate and latency
    • Error rates by error type
    • Batch completion time
    • Beneficiary complaints or queries

    Maintain direct communication with PSP support during the first distribution.

Transaction monitoring

After go-live, implement ongoing transaction monitoring to detect issues before they affect beneficiaries.

Status reconciliation

Run daily reconciliation between your CVA platform and PSP records. Query payments submitted in the last 24 hours and compare status:

-- CVA platform: payments awaiting PSP confirmation
SELECT transfer_id, psp_payment_id, amount, submitted_at
FROM transfers
WHERE status = 'pending'
AND submitted_at < NOW() - INTERVAL '4 hours';
-- These payments should have received status updates
-- Investigate any pending longer than 4 hours

For payments without status updates, query the PSP API directly:

Terminal window
curl -X GET "https://api.psp-example.com/api/v2/payments/pay_abc123" \
-H "Authorization: Bearer $ACCESS_TOKEN"

If the PSP shows a different status than your platform, investigate webhook delivery failures.

Error rate monitoring

Track error rates to detect systemic issues:

Alert thresholds:
- Error rate > 5% of batch: Investigate immediately
- Error rate > 2% of batch: Review within 4 hours
- Any authentication error: Alert immediately
- Any rate limit error: Reduce submission rate

Configure alerts in your monitoring system:

# Example Prometheus alert rule
groups:
- name: psp_integration
rules:
- alert: HighPaymentErrorRate
expr: |
rate(psp_payments_failed_total[5m]) /
rate(psp_payments_submitted_total[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "PSP payment error rate exceeds 5%"

Reconciliation workflow

+-------------------------------------------------------------------+
| DAILY RECONCILIATION |
+-------------------------------------------------------------------+
| |
| +------------------+ |
| | CVA Platform | |
| | Export +----+ |
| | (submitted, | | |
| | completed, | | |
| | failed) | | |
| +------------------+ | |
| | +------------------+ |
| +--->| Reconciliation | |
| | | Engine | |
| +------------------+ | | | |
| | PSP Report +----+ | - Match by ID | |
| | Download | | - Compare status | |
| | (all transactions| | - Flag mismatches| |
| | for period) | +--------+---------+ |
| +------------------+ | |
| | |
| +-------------+-------------+ |
| | | | |
| v v v |
| +------+-----+ +-----+------+ +----+-------+ |
| | Matched | | CVA Only | | PSP Only | |
| | (verify | | (webhook | | (missing | |
| | amounts) | | failure) | | in CVA) | |
| +------------+ +------------+ +------------+ |
| |
+-------------------------------------------------------------------+

Mismatches require investigation:

  • CVA only (no PSP record): Submission failed silently; verify with PSP support
  • PSP only (no CVA record): Duplicate submission or data corruption; investigate CVA logs
  • Status mismatch: Webhook delivery failure; manually update CVA platform
  • Amount mismatch: Serious error; halt processing and investigate

Error handling

Transient errors

Transient errors resolve with retry. Implement automatic retry with backoff:

+-------------------------------------------------------------------+
| ERROR HANDLING FLOW |
+-------------------------------------------------------------------+
| |
| +------------------+ |
| | Payment | |
| | Submission | |
| +--------+---------+ |
| | |
| v |
| +--------+---------+ |
| | HTTP Response? | |
| +--------+---------+ |
| | |
| +-------------------+-------------------+ |
| | | | |
| v v v |
| +------+------+ +------+------+ +------+------+ |
| | 2xx | | 4xx | | 5xx/Timeout | |
| | Success | | Client Error| | Server Error| |
| +------+------+ +------+------+ +------+------+ |
| | | | |
| v v v |
| +------+------+ +------+------+ +------+------+ |
| | Update | | 400: Log | | Retry with | |
| | status to | | and | | exponential | |
| | pending | | review | | backoff | |
| +-------------+ | 401: Alert | +------+------+ |
| | immed. | | |
| | 429: Wait | v |
| | retry | +------+------+ |
| +-------------+ | Max retries | |
| | exceeded? | |
| +------+------+ |
| | |
| +----------------+--------+ |
| | | |
| v v |
| +------+------+ +-------+-----+ |
| | No: Retry | | Yes: Flag | |
| | after delay | | for manual | |
| +-------------+ | review | |
| +-------------+ |
| |
+-------------------------------------------------------------------+

Permanent errors

Permanent errors require manual intervention. Common permanent errors:

Error codePSP messageCauseResolution
invalid_accountAccount does not existBeneficiary phone/account number incorrectVerify with beneficiary, update record
account_blockedAccount is blockedBeneficiary account suspended by providerContact beneficiary for alternative
amount_below_minimumAmount below minimumTransfer amount less than PSP minimumAdjust amount or combine with other transfer
amount_above_maximumAmount exceeds limitTransfer amount exceeds PSP or regulatory limitSplit into multiple transfers
duplicate_referenceReference already usedReference number reusedGenerate new reference
invalid_currencyCurrency not supportedCurrency not enabled for your merchant accountContact PSP to enable currency

Verification

After completing integration, verify the full flow works correctly:

Single payment verification

Terminal window
# Submit payment
PAYMENT_ID=$(curl -s -X POST "https://api.psp-example.com/api/v2/payments" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: verify-$(date +%s)" \
-d '{
"recipient": {"external_id": "VERIFY-001", "mobile_number": "+255700000001"},
"transaction": {"amount": 50000, "currency": "TZS", "reference": "VERIFY-001"}
}' | jq -r '.id')
echo "Payment ID: $PAYMENT_ID"
# Wait for processing
sleep 30
# Check status
curl -s "https://api.psp-example.com/api/v2/payments/$PAYMENT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.status'
# Expected: "completed"

Webhook verification

Check webhook logs for received callbacks:

Terminal window
# In CVA platform logs or webhook logging service
grep "payment.completed" /var/log/cva/webhooks.log | tail -5
# Expected: Recent entries with payment IDs matching submitted payments

CVA platform status verification

Confirm the CVA platform reflects current PSP status:

SELECT t.transfer_id, t.status, t.psp_payment_id, t.psp_reference,
t.submitted_at, t.completed_at
FROM transfers t
WHERE t.submitted_at > NOW() - INTERVAL '1 hour'
ORDER BY t.submitted_at DESC
LIMIT 10;
-- All completed payments should show:
-- status = 'completed'
-- psp_reference populated
-- completed_at populated

End-to-end timing verification

Measure processing time to establish baseline:

Timing benchmarks (typical):
- Submission to PSP acknowledgment: < 2 seconds
- PSP acknowledgment to completion webhook: 5-60 seconds (mobile money)
- PSP acknowledgment to completion webhook: 1-24 hours (bank transfer)
- Total end-to-end (mobile money): < 2 minutes

If timing exceeds these benchmarks consistently, investigate network latency or PSP performance.

Troubleshooting

SymptomCauseResolution
401 Unauthorized on all requestsInvalid or expired API credentialsVerify credentials match PSP portal; regenerate if necessary
401 Unauthorized after period of successOAuth token expired and not refreshedImplement token refresh before expiry; check refresh logic
403 ForbiddenIP not allowlisted, or account suspendedCheck IP allowlist with PSP; contact PSP support
429 Too Many RequestsRate limit exceededReduce submission rate; implement backoff; check Retry-After header
400 Bad Request with field validation errorField mapping incorrect or data invalidCheck error response for specific field; verify mapping
Webhook not receivedFirewall blocking PSP, webhook URL incorrect, or SSL issueTest webhook URL accessibility; check SSL certificate; verify URL in PSP config
Webhook received but signature invalidWrong signing secret configuredVerify signing secret matches PSP webhook configuration
Payment stuck in pendingPSP processing delay or missed webhookQuery PSP API directly for status; check webhook logs
Duplicate payments createdIdempotency key not sent or not uniqueVerify idempotency key header; ensure key generation creates unique values
Status mismatch between CVA and PSPWebhook processing failedCompare records; manually update CVA platform; investigate webhook handler errors
Connection timeout on submissionNetwork issue or PSP availabilityCheck network connectivity; verify PSP status page; implement retry
SSL certificate errorExpired certificate or hostname mismatchRenew certificate; verify hostname matches certificate
Amount rejected as too lowBelow PSP minimum transaction amountCheck PSP minimum limits; adjust transfer amount
Amount rejected as too highExceeds PSP or regulatory limitCheck limits; split into multiple payments
Currency rejectedCurrency not enabled for merchant accountContact PSP to enable currency; verify currency code format
Beneficiary account not foundPhone number format incorrectVerify phone number format matches PSP requirements (with/without country code)

Diagnostic commands

Check PSP API availability:

Terminal window
curl -w "\nHTTP Status: %{http_code}\nTime: %{time_total}s\n" \
-o /dev/null -s \
"https://api.psp-example.com/api/v2/health"

Test authentication:

Terminal window
curl -s -X GET "https://api.psp-example.com/api/v2/account" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq '{merchant_id, status, balance}'

Check webhook endpoint accessibility (from external network):

Terminal window
curl -w "\nHTTP Status: %{http_code}\n" \
-X POST "https://cva.example.org/api/webhooks/psp-status" \
-H "Content-Type: application/json" \
-d '{"test": true}'
# Should return 200 or 401 (if signature required), not connection error

Review recent webhook deliveries in PSP portal or via API:

Terminal window
curl -s "https://api.psp-example.com/api/v2/webhooks/whk_abc123/deliveries?limit=10" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq '.[] | {id, status, response_code, delivered_at}'

Rollback

If production issues require reverting to previous state:

  1. Disable the PSP integration in CVA platform configuration (toggle off or remove credentials)
  2. Document all payments submitted since go-live with their current status
  3. Coordinate with PSP support to halt processing of pending payments if possible
  4. Restore previous integration configuration if reverting to different PSP
  5. Communicate with programme teams about payment status and timeline

Do not delete payment records; mark them for reconciliation review.

See also