Payment reconciliation
This guide helps you reconcile all payment types—hosted payment sessions, API-integrated payments, third-party payments, and non-third-party payments—with your internal systems by tracking webhook events and matching them to your records. This ensures complete visibility into payment status and enables accurate financial reporting.
Payment reconciliation is critical for maintaining accurate financial records and providing customers with real-time payment status updates. This guide applies to all payment integration approaches:
- Hosted payment sessions: Payments created via hosted payment session API
- API-integrated payments: Payments created via direct API calls
- Third-party payments: Payments funded by someone other than the account holder
- Non-third-party payments: Payments funded by the account holder from their own account
This guide assumes you have already integrated Redpin’s payment APIs and are receiving webhook notifications.
Learn about hosted vs API integration approaches
Complete API specification for payment sessions
Complete API specification for direct payment integration
Detailed webhook payload schemas
Comprehensive error handling patterns
Reconciliation keys and identifiers
Understanding the identifiers in the payment flow is essential for proper reconciliation. Each identifier serves a specific purpose in matching webhooks to your internal records.
Use client_reference_id as your primary reconciliation key when present. For third-party payments, it is required and must be unique per customer (per Redpin customer_id); duplicates for the same customer will be rejected. For non-third-party API payments, it is optional and uniqueness is not enforced.
Field availability differs by payment type:
- Hosted sessions:
session_idis always present;client_customer_refandclient_reference_idare present - API Payment (Third-Party):
session_idis NOT present;client_customer_refandclient_reference_idare present - API Payment (Non-Third-Party):
session_idis NOT present;client_customer_refandclient_reference_idmay be present if provided
The payment_id appears in the first webhook (AWAITING_FUNDS or PROCESSING) for all payment types and should be your primary tracking identifier.
Payment creation and initial storage
When creating a payment (via hosted session or direct API), store the key identifiers that will enable reconciliation when webhooks arrive.
Hosted Payment Sessions
API-Integrated Payments
For hosted payment sessions, store identifiers immediately after session creation:
Recommended database schema
Store these fields when you create a payment (hosted session or API) to enable webhook matching:
For hosted sessions: Store session_id, client_customer_ref, client_reference_id, and all items immediately after session creation.
For API-integrated payments: Store client_reference_id (required for third-party, recommended for non-third-party), client_customer_ref (for third-party only), and payment details immediately after payment creation.
The payment_id will appear in the first webhook (AWAITING_FUNDS or PROCESSING) for all payment types. Use client_reference_id as your primary reconciliation key to match webhooks to your records.
Reconciliation flow diagram
The following diagram shows the complete reconciliation flow from the client system perspective. Each step shows the action your system must take to maintain accurate payment records.
Understanding the flow
The diagram above shows the FX (different-currency) flow: AWAITING_FUNDS → RECEIVED_FUNDS → FX_COMPLETED → PAYOUT_INITIATED → PAYOUT_CREDITED → PAYMENT_COMPLETED. For same-currency payments (no FX), the first webhook is PROCESSING (not AWAITING_FUNDS), then PROCESSING → PAYOUT_INITIATED → PAYOUT_CREDITED → PAYMENT_COMPLETED (or PROCESSING → CANCELLED).
The reconciliation flow consists of six main stages:
1. Session Creation
- Your system creates a hosted payment session via API (v2)
- Store
session_id,client_reference_id,client_customer_ref, and all items immediately - Each item can have an
item_reffor granular reconciliation (invoice numbers, property IDs, booking references, etc.) - Redirect customer to the
session_urlto complete payment
2. First Webhook (AWAITING_FUNDS or PROCESSING)
- FX payments: AWAITING_FUNDS — contains
payment_idfor the first time, along withclient_reference_id. Match to your session usingclient_reference_id, mappayment_idtosession_id, update status to AWAITING_FUNDS. - Same-currency payments: PROCESSING — sent immediately after payment creation. Contains
payment_id,customer_id, and (when provided)client_reference_idandclient_customer_ref. Mappayment_idto your record and update status to PROCESSING.
3. Status Updates
- Receive webhooks for RECEIVED_FUNDS, FX_COMPLETED, PAYOUT_INITIATED, PAYOUT_CREDITED
- Update status in your database at each milestone
- Store relevant data (amounts, conversion rates, recipient IDs)
4. Multi-Recipient Logic
- For single-recipient payments, PAYOUT_CREDITED is followed by PAYMENT_COMPLETED
- For multi-recipient payments, track each
recipient_idstatus separately - PAYMENT_COMPLETED fires only when all recipients are paid
5. Completion
- PAYMENT_COMPLETED webhook indicates full payment success
- Mark the payment as fully reconciled
- Trigger any post-payment workflows (notifications, accounting updates)
6. Error Handling
- CANCELLED: Payment cancelled before or during processing
- REFUNDED: Funds refunded to customer after receipt
- BOUNCED_BACK: Payout failed and funds returned
- Log errors, notify operations team, handle customer communication
Webhook processing and status updates
Process each webhook status systematically to maintain accurate payment records. All webhooks contain event_id, payment_id, status, customer_id, client_customer_ref, event_timestamp, and data.
AWAITING_FUNDS
When: Session created, waiting for customer to send funds
Webhook contains:
payment_id(first time this ID appears)client_reference_id(your unique payment reference - use this to match!)client_customer_ref(your customer identifier)data: (empty)
Action:
- Match the webhook to your session using
client_reference_id - Store the
payment_idand link it to yoursession_id - Update status to AWAITING_FUNDS
- Display “Payment initiated, awaiting funds” to customer
PROCESSING
When: Same-currency payment accepted (no FX); sent immediately after payment creation
Webhook contains:
payment_id(first time this ID appears for same-currency payments)client_reference_id(when provided)client_customer_ref(when provided)data: (empty)
Action:
- Match the webhook to your payment record using
client_reference_idorpayment_id - Store or link the
payment_idto your record - Update status to PROCESSING
- Expect next webhooks: PAYOUT_INITIATED → PAYOUT_CREDITED → PAYMENT_COMPLETED
RECEIVED_FUNDS
When: Customer’s funds received in Redpin wallet
Webhook contains:
payment_iddata.amount.currency: Currency of funds receiveddata.amount.value: Amount received
Action:
- Find payment by
payment_idin your database - Update status to RECEIVED_FUNDS
- Store amount received for reconciliation
- Display “Funds received, processing conversion” to customer
FX_COMPLETED
When: Currency conversion completed
Webhook contains:
payment_iddata.sell_amount: Source currency and amountdata.buy_amount: Destination currency and amountdata.quote_rate: Exchange rate used
Action:
- Find payment by
payment_id - Update status to FX_COMPLETED
- Store conversion details for audit trail
- Display “Conversion complete, initiating payout” to customer
PAYOUT_INITIATED
When: Transfer to recipient started
Webhook contains:
payment_iddata.amount: Amount being sent to recipientdata.recipient_id: Unique identifier for the recipient
Action:
- Find payment by
payment_id - Update status to PAYOUT_INITIATED
- Track
recipient_id(especially important for multi-recipient payments) - Display “Payout initiated to recipient” to customer
PAYOUT_CREDITED
When: Recipient’s bank account credited
Webhook contains:
payment_iddata.amount: Amount credited to recipientdata.recipient_id: Unique identifier for the recipient
Action:
- Find payment by
payment_id - Mark this specific
recipient_idas PAID - If multiple recipients, check if all are paid
- If single recipient, wait for PAYMENT_COMPLETED
- Display “Funds delivered to recipient” to customer
PAYMENT_COMPLETED
When: All recipients paid, payment fully complete
Webhook contains:
payment_iddata.recipient_details: Array of all recipients with amounts and IDs
Action:
- Find payment by
payment_id - Update status to PAYMENT_COMPLETED
- Mark as fully reconciled in your system
- Display “Payment complete” to customer
- Trigger post-payment workflows (accounting, notifications)
For single-recipient payments, both PAYOUT_CREDITED and PAYMENT_COMPLETED will be delivered. For multi-recipient payments, PAYOUT_CREDITED fires once per recipient, and PAYMENT_COMPLETED fires only when all recipients have been paid.
Handling multiple recipients
Payments with multiple recipients require special handling to track each recipient’s status individually.
When a payment has multiple recipients:
- PAYOUT_INITIATED fires once per recipient (with
recipient_id) - PAYOUT_CREDITED fires once per recipient (with
recipient_id) - PAYMENT_COMPLETED fires once when all recipients are paid
Multi-recipient tracking logic
Maintain a separate table to track individual recipient status:
Example reconciliation logic:
Track recipient_id payment status in a separate table for a complete audit trail. This enables you to show customers the status of each individual payout in multi-recipient scenarios.
Error handling and edge cases
Handle error states and edge cases gracefully to maintain system reliability.
CANCELLED status
When: Payment cancelled before or during processing
Webhook contains:
payment_iddata.reason_description: Reason for cancellation
Action:
- Update status to CANCELLED
- Store cancellation reason
- Process refund logic if applicable
- Notify customer of cancellation
REFUNDED status
When: Funds refunded to customer after receipt
Webhook contains:
payment_iddata.refund_amount: Amount refundeddata.refund_reason: Reason for refund
Action:
- Update status to REFUNDED
- Store refund amount and reason
- Notify customer of refund
- Update accounting records
BOUNCED_BACK status
When: Payout failed and funds returned
Webhook contains:
payment_iddata.amount: Amount that bounceddata.recipient_id: Affected recipientdata.bounce_reason: Why the payout failed
Action:
- Update status to BOUNCED_BACK
- Store bounce reason and affected recipient
- Investigate the cause (invalid account, incorrect details)
- Contact support if needed
- Prepare retry with corrected details
Unmatched webhooks
When: Webhook arrives but cannot match to any session
Possible causes:
- Session not stored before webhook arrives
client_customer_refmismatch- Database sync lag
Action:
- Log the unmatched webhook with full payload
- Alert operations team immediately
- Investigate root cause (timing issue, data mismatch, system error)
- If legitimate, manually reconcile the payment
Out-of-order webhooks
When: Webhooks arrive in unexpected sequence
Why it happens:
- Network delays
- Retry logic
- Processing variations
Action:
- Use
event_timestampto determine actual event order - Handle webhooks idempotently (processing same webhook twice should be safe)
- Do not assume strict ordering in your logic
- Update status based on timestamp, not arrival order
Duplicate webhooks
When: Same event_id received multiple times
Why it happens:
- Webhook delivery retry mechanism
- Network issues causing redelivery
Action:
- Check
event_idbefore processing any webhook - If
event_idalready exists in your webhook_events table, skip processing - Return 200 OK to acknowledge receipt (prevents further retries)
- Log duplicate detection for monitoring
Always store event_id in your webhook events table with a unique constraint to detect and skip duplicate deliveries. This prevents double-processing payments and ensures data integrity.
Webhooks use Svix for delivery. See the Webhooks overview for signature verification implementation.
Best practices
Follow these best practices for reliable payment reconciliation:
Use client_reference_id as primary reconciliation key
This is your unique payment reference (max 100 chars, alphanumeric with hyphens and underscores). It ensures idempotency across session creation retries - duplicate references will be rejected with a 400 error. This makes matching internal records straightforward and prevents accidental duplicate payments.
Store full webhook payload in audit table
Preserve the complete event history by storing the entire JSON payload. This enables debugging, forensic analysis, and compliance audits. Include event_id, payment_id, status, and the full payload field.
Implement webhook signature verification
Validate the authenticity of webhooks to prevent fraudulent requests. See the Webhooks overview for implementation details using Svix signatures.
Log unmatched webhooks for operations review
Create alerts for webhooks that do not match any session in your database. This may indicate data sync issues, timing problems, or system errors that require investigation.
Set up status-based monitoring alerts
Monitor payment progress and alert on anomalies:
- Alert if payment stuck in AWAITING_FUNDS for more than 24 hours
- Alert if FX_COMPLETED but no PAYOUT_INITIATED within expected timeframe
- Alert immediately for BOUNCED_BACK or CANCELLED statuses
- Alert if PAYOUT_CREDITED received but PAYMENT_COMPLETED never arrives
Reconcile against bank statements regularly
Match payment_id records against actual bank movements daily or weekly. Identify discrepancies early and resolve them with the Partner Integrations Team.
Maintain complete audit trail with timestamps
Log all status transitions with timestamps and track who initiated actions (system vs manual). This provides a complete audit trail for compliance and debugging.
Handle webhook processing idempotently
Use event_id to prevent duplicate processing. Ensure operations are safe to retry. Use database transactions to ensure atomic updates.
Testing reconciliation in sandbox
Test your reconciliation logic thoroughly in the sandbox environment before going to production.
Test 1: Single recipient success flow
Setup: Create session with 1 recipient
Expected webhooks:
- AWAITING_FUNDS
- RECEIVED_FUNDS
- FX_COMPLETED
- PAYOUT_INITIATED
- PAYOUT_CREDITED
- PAYMENT_COMPLETED
Validation:
- Verify each status transition is stored correctly
- Confirm
payment_idmapped tosession_idon first webhook - Check all amounts and conversion rates stored accurately
Test 2: Multiple recipients success flow
Setup: Create session with 3 recipients
Expected webhooks:
- AWAITING_FUNDS
- RECEIVED_FUNDS
- FX_COMPLETED
- PAYOUT_INITIATED (recipient 1)
- PAYOUT_INITIATED (recipient 2)
- PAYOUT_INITIATED (recipient 3)
- PAYOUT_CREDITED (recipient 1)
- PAYOUT_CREDITED (recipient 2)
- PAYOUT_CREDITED (recipient 3)
- PAYMENT_COMPLETED
Validation:
- Verify PAYOUT_CREDITED fires 3 times (once per recipient)
- Confirm each
recipient_idtracked separately - Check PAYMENT_COMPLETED fires only after all recipients paid
Test 3: Payment cancellation
Setup: Create session but cancel before completion
Expected webhooks:
- AWAITING_FUNDS
- CANCELLED
Validation:
- Verify cancellation reason stored
- Confirm customer notification sent
- Check no further webhooks received after CANCELLED
Test 4: Unmatched webhook handling
Setup: Manually send webhook with unknown payment_id
Expected behavior:
- System logs unmatched event
- Operations team receives alert
- No status update to any payment
- 200 OK returned to webhook sender
Validation:
- Confirm unmatched event logged with full payload
- Verify alert triggers correctly
- Check system remains stable
Test 5: Duplicate webhook detection
Setup: Process same webhook twice (same event_id)
Expected behavior:
- First delivery: Webhook processed, status updated
- Second delivery: Webhook skipped, no duplicate status update
- Both deliveries: 200 OK returned
Validation:
- Verify
event_iddeduplication works - Confirm no duplicate status updates in database
- Check idempotency maintained
In sandbox, webhooks are delivered instantly for testing. In production, expect slight delays based on payment processing time (typically seconds to minutes for each stage).
Troubleshooting
Common reconciliation issues and their solutions.
Problem: Webhook not received
Symptoms: Expected webhook never arrives
Likely causes:
- Webhook URL not publicly accessible
- Signature verification failing (webhook rejected by your endpoint)
- Firewall blocking Svix delivery IPs
- Incorrect webhook URL configured
Solutions:
- Verify webhook URL returns 200 OK when called
- Check webhook endpoint logs for rejected requests
- Review signature verification implementation
- Ensure URL uses HTTPS (HTTP may be blocked)
- Check firewall rules allow Svix IP ranges
- Review webhook delivery logs in Redpin dashboard (if available)
Problem: Cannot match payment_id to session
Symptoms: AWAITING_FUNDS webhook arrives but cannot find matching session
Likely causes:
client_reference_idmismatch between session and webhook- Session not stored before webhook arrives (timing issue)
- Database sync lag in replicated environment
Solutions:
- Log
client_reference_idfrom webhook and compare to database records - Ensure session stored synchronously before redirecting customer
- Check database replication lag if using read replicas
- Verify
client_reference_idformat consistency (trimming, case sensitivity) - Add retry logic to handle temporary timing issues
Problem: Payment stuck in status
Symptoms: Status has not updated in 24+ hours
Likely causes:
- Customer did not complete session flow (abandoned at hosted page)
- Payment awaiting manual review or compliance check
- Technical issue on Redpin side
Solutions:
- Check if customer completed session flow (check session expiration)
- Contact Partner Integrations Team with
payment_idandsession_id - Review webhook logs for missed deliveries
- Check customer communication (email bounces, failed notifications)
- Verify payment not stuck in AWAITING_FUNDS due to customer inaction
Problem: Multiple PAYOUT_CREDITED but no PAYMENT_COMPLETED
Symptoms: Received PAYOUT_CREDITED for all recipients but PAYMENT_COMPLETED never arrives
Likely causes:
- This is expected behavior, wait longer (up to 5 minutes)
- Webhook delivery delay or failure
- One recipient payout still pending
Solutions:
- Wait up to 5 minutes after last PAYOUT_CREDITED webhook
- Verify all expected recipients received PAYOUT_CREDITED
- Check webhook delivery logs if still missing after 5 minutes
- Contact support if issue persists beyond 10 minutes
- Review recipient count in original session request
Problem: Duplicate webhooks causing issues
Symptoms: Same status processed multiple times, duplicate database entries
Likely causes:
- Not checking
event_idfor idempotency - Webhook processing not transactional
- Race condition in concurrent webhook processing
Solutions:
- Implement
event_iddeduplication check before processing - Use database transactions for webhook processing
- Add unique constraint on
event_idin webhook_events table - Implement distributed locks for concurrent webhook processing
- Return 200 OK even for duplicate events (prevents further retries)
If payment stuck for more than 24 hours, contact the Partner Integrations Team immediately at apisupport@redpincompany.com with your payment_id and session_id. Include recent webhook history and customer status in your report.
Next steps and related resources
Now that you understand reconciliation, explore these related resources:
Complete API specification for sessions
Detailed webhook payload schemas
Hosted vs API integration approaches
Comprehensive error handling patterns
For integration support or questions about reconciliation, contact the Partner Integrations Team at apisupport@redpincompany.com.