> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.redpincompany.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.redpincompany.com/_mcp/server.

# Payment reconciliation

> Guide to reconciling all payment types (hosted sessions, API-integrated []third-party, non-third-party]) with your internal systems

> **Info**
>
> 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.

#### [Integration guidelines](/api-guide/partners/integration-guidelines)

Learn about hosted vs API integration approaches

#### [Hosted API reference](/api-reference/hosted-experience)

Complete API specification for payment sessions

#### [B2B API reference](/api-reference/customers/api-integration)

Complete API specification for direct payment integration

#### [Webhooks reference](/api-reference/webhooks)

Detailed webhook payload schemas

#### [Error handling](/api-guide/getting-started/error-handling)

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.

| Identifier            | Source                                         | Available For                                | Reconciliation Use                                                        |
| --------------------- | ---------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
| `session_id`          | Returned in session creation response          | Hosted sessions only                         | Link webhooks to original session                                         |
| `payment_id`          | First appears in AWAITING\_FUNDS webhook       | All payment types                            | Primary payment tracking ID in Redpin system                              |
| `client_customer_ref` | Provided by you in payment/session request     | Third-party payments only                    | Your customer identifier                                                  |
| `client_reference_id` | Provided by you in payment/session request     | All payment types (required for third-party) | Your payment reference; for third-party only, must be unique per customer |
| `item_ref`            | Optional field in items array                  | Hosted sessions only                         | Track individual line items (invoices, properties, bookings, etc.)        |
| `recipient_id`        | Appears in PAYOUT\_INITIATED/CREDITED webhooks | All payment types                            | Track individual recipients in multi-recipient payments                   |
| `event_id`            | Included in every webhook                      | All payment types                            | Idempotency key to prevent duplicate processing                           |

> **Tip**
>
> 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.

> **Warning**
>
> **Field availability differs by payment type:**
>
> * **Hosted sessions**: `session_id` is always present; `client_customer_ref` and `client_reference_id` are present
> * **API Payment (Third-Party)**: `session_id` is NOT present; `client_customer_ref` and `client_reference_id` are present
> * **API Payment (Non-Third-Party)**: `session_id` is NOT present; `client_customer_ref` and `client_reference_id` may 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

For hosted payment sessions, store identifiers immediately after session creation:

### Request

POST [https://api.redpincompany.com/v1/customers/\{customer\_id}/sessions](https://api.redpincompany.com/v1/customers/\{customer_id}/sessions)

**`Single property with deposit, installment and platform fees`**

```curl Single property with deposit, installment and platform fees
curl -X POST https://api.redpincompany.com/v1/customers/customer_123/sessions \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "body": {
    "client_customer_ref": "CUST-1002-TXN",
    "client_reference_id": "PAY-2025-08-20-002",
    "amount": {
      "currency": "AED",
      "value": 87500
    },
    "due_date": "2025-08-20",
    "items": [
      {
        "item_name": "Boulevard Residences - Unit 805 - Initial deposit",
        "item_ref": "INV-2025-10450",
        "item_type": "deposit",
        "amount": {
          "currency": "AED",
          "value": 50000
        }
      },
      {
        "item_name": "Boulevard Residences - Unit 805 - Installment 1 of 10",
        "item_ref": "INV-2025-10451",
        "item_type": "installment",
        "amount": {
          "currency": "AED",
          "value": 30000
        }
      },
      {
        "item_name": "Platform transaction fee",
        "item_ref": "INV-2025-10452",
        "item_type": "platform_fee",
        "amount": {
          "currency": "AED",
          "value": 5000
        }
      },
      {
        "item_name": "Agent commission",
        "item_ref": "INV-2025-10453",
        "item_type": "commission",
        "amount": {
          "currency": "AED",
          "value": 2500
        }
      }
    ],
    "recipient_details": [
      {
        "amount": {
          "currency": "AED",
          "value": 80000
        },
        "payment_reference": "BLV-805-DEPOSIT-INS1",
        "purpose_of_transaction": "PROPERTY_PURCHASE",
        "recipient_id": "123456"
      },
      {
        "amount": {
          "currency": "AED",
          "value": 5000
        },
        "payment_reference": "PLATFORM-FEE-AUG",
        "purpose_of_transaction": "BILL_PAYMENTS",
        "recipient_id": "654321"
      },
      {
        "amount": {
          "currency": "AED",
          "value": 2500
        },
        "payment_reference": "AGENT-COMM-AUG",
        "purpose_of_transaction": "PROPERTY_PURCHASE",
        "recipient_id": "789012"
      }
    ],
    "allowed_origins": [
      "https://app.example.com"
    ]
  }
}'
```

**`Single property with deposit, installment and platform fees`**

```python Single property with deposit, installment and platform fees
import requests

url = "https://api.redpincompany.com/v1/customers/customer_123/sessions"

payload = { "body": {
        "client_customer_ref": "CUST-1002-TXN",
        "client_reference_id": "PAY-2025-08-20-002",
        "amount": {
            "currency": "AED",
            "value": 87500
        },
        "due_date": "2025-08-20",
        "items": [
            {
                "item_name": "Boulevard Residences - Unit 805 - Initial deposit",
                "item_ref": "INV-2025-10450",
                "item_type": "deposit",
                "amount": {
                    "currency": "AED",
                    "value": 50000
                }
            },
            {
                "item_name": "Boulevard Residences - Unit 805 - Installment 1 of 10",
                "item_ref": "INV-2025-10451",
                "item_type": "installment",
                "amount": {
                    "currency": "AED",
                    "value": 30000
                }
            },
            {
                "item_name": "Platform transaction fee",
                "item_ref": "INV-2025-10452",
                "item_type": "platform_fee",
                "amount": {
                    "currency": "AED",
                    "value": 5000
                }
            },
            {
                "item_name": "Agent commission",
                "item_ref": "INV-2025-10453",
                "item_type": "commission",
                "amount": {
                    "currency": "AED",
                    "value": 2500
                }
            }
        ],
        "recipient_details": [
            {
                "amount": {
                    "currency": "AED",
                    "value": 80000
                },
                "payment_reference": "BLV-805-DEPOSIT-INS1",
                "purpose_of_transaction": "PROPERTY_PURCHASE",
                "recipient_id": "123456"
            },
            {
                "amount": {
                    "currency": "AED",
                    "value": 5000
                },
                "payment_reference": "PLATFORM-FEE-AUG",
                "purpose_of_transaction": "BILL_PAYMENTS",
                "recipient_id": "654321"
            },
            {
                "amount": {
                    "currency": "AED",
                    "value": 2500
                },
                "payment_reference": "AGENT-COMM-AUG",
                "purpose_of_transaction": "PROPERTY_PURCHASE",
                "recipient_id": "789012"
            }
        ],
        "allowed_origins": ["https://app.example.com"]
    } }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

**`Single property with deposit, installment and platform fees`**

```javascript Single property with deposit, installment and platform fees
const url = 'https://api.redpincompany.com/v1/customers/customer_123/sessions';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"body":{"client_customer_ref":"CUST-1002-TXN","client_reference_id":"PAY-2025-08-20-002","amount":{"currency":"AED","value":87500},"due_date":"2025-08-20","items":[{"item_name":"Boulevard Residences - Unit 805 - Initial deposit","item_ref":"INV-2025-10450","item_type":"deposit","amount":{"currency":"AED","value":50000}},{"item_name":"Boulevard Residences - Unit 805 - Installment 1 of 10","item_ref":"INV-2025-10451","item_type":"installment","amount":{"currency":"AED","value":30000}},{"item_name":"Platform transaction fee","item_ref":"INV-2025-10452","item_type":"platform_fee","amount":{"currency":"AED","value":5000}},{"item_name":"Agent commission","item_ref":"INV-2025-10453","item_type":"commission","amount":{"currency":"AED","value":2500}}],"recipient_details":[{"amount":{"currency":"AED","value":80000},"payment_reference":"BLV-805-DEPOSIT-INS1","purpose_of_transaction":"PROPERTY_PURCHASE","recipient_id":"123456"},{"amount":{"currency":"AED","value":5000},"payment_reference":"PLATFORM-FEE-AUG","purpose_of_transaction":"BILL_PAYMENTS","recipient_id":"654321"},{"amount":{"currency":"AED","value":2500},"payment_reference":"AGENT-COMM-AUG","purpose_of_transaction":"PROPERTY_PURCHASE","recipient_id":"789012"}],"allowed_origins":["https://app.example.com"]}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

**`Single property with deposit, installment and platform fees`**

```go Single property with deposit, installment and platform fees
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.redpincompany.com/v1/customers/customer_123/sessions"

	payload := strings.NewReader("{\n  \"body\": {\n    \"client_customer_ref\": \"CUST-1002-TXN\",\n    \"client_reference_id\": \"PAY-2025-08-20-002\",\n    \"amount\": {\n      \"currency\": \"AED\",\n      \"value\": 87500\n    },\n    \"due_date\": \"2025-08-20\",\n    \"items\": [\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Initial deposit\",\n        \"item_ref\": \"INV-2025-10450\",\n        \"item_type\": \"deposit\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 50000\n        }\n      },\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Installment 1 of 10\",\n        \"item_ref\": \"INV-2025-10451\",\n        \"item_type\": \"installment\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 30000\n        }\n      },\n      {\n        \"item_name\": \"Platform transaction fee\",\n        \"item_ref\": \"INV-2025-10452\",\n        \"item_type\": \"platform_fee\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        }\n      },\n      {\n        \"item_name\": \"Agent commission\",\n        \"item_ref\": \"INV-2025-10453\",\n        \"item_type\": \"commission\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        }\n      }\n    ],\n    \"recipient_details\": [\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 80000\n        },\n        \"payment_reference\": \"BLV-805-DEPOSIT-INS1\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"123456\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        },\n        \"payment_reference\": \"PLATFORM-FEE-AUG\",\n        \"purpose_of_transaction\": \"BILL_PAYMENTS\",\n        \"recipient_id\": \"654321\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        },\n        \"payment_reference\": \"AGENT-COMM-AUG\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"789012\"\n      }\n    ],\n    \"allowed_origins\": [\n      \"https://app.example.com\"\n    ]\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

**`Single property with deposit, installment and platform fees`**

```ruby Single property with deposit, installment and platform fees
require 'uri'
require 'net/http'

url = URI("https://api.redpincompany.com/v1/customers/customer_123/sessions")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"body\": {\n    \"client_customer_ref\": \"CUST-1002-TXN\",\n    \"client_reference_id\": \"PAY-2025-08-20-002\",\n    \"amount\": {\n      \"currency\": \"AED\",\n      \"value\": 87500\n    },\n    \"due_date\": \"2025-08-20\",\n    \"items\": [\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Initial deposit\",\n        \"item_ref\": \"INV-2025-10450\",\n        \"item_type\": \"deposit\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 50000\n        }\n      },\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Installment 1 of 10\",\n        \"item_ref\": \"INV-2025-10451\",\n        \"item_type\": \"installment\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 30000\n        }\n      },\n      {\n        \"item_name\": \"Platform transaction fee\",\n        \"item_ref\": \"INV-2025-10452\",\n        \"item_type\": \"platform_fee\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        }\n      },\n      {\n        \"item_name\": \"Agent commission\",\n        \"item_ref\": \"INV-2025-10453\",\n        \"item_type\": \"commission\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        }\n      }\n    ],\n    \"recipient_details\": [\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 80000\n        },\n        \"payment_reference\": \"BLV-805-DEPOSIT-INS1\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"123456\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        },\n        \"payment_reference\": \"PLATFORM-FEE-AUG\",\n        \"purpose_of_transaction\": \"BILL_PAYMENTS\",\n        \"recipient_id\": \"654321\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        },\n        \"payment_reference\": \"AGENT-COMM-AUG\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"789012\"\n      }\n    ],\n    \"allowed_origins\": [\n      \"https://app.example.com\"\n    ]\n  }\n}"

response = http.request(request)
puts response.read_body
```

**`Single property with deposit, installment and platform fees`**

```java Single property with deposit, installment and platform fees
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.redpincompany.com/v1/customers/customer_123/sessions")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"body\": {\n    \"client_customer_ref\": \"CUST-1002-TXN\",\n    \"client_reference_id\": \"PAY-2025-08-20-002\",\n    \"amount\": {\n      \"currency\": \"AED\",\n      \"value\": 87500\n    },\n    \"due_date\": \"2025-08-20\",\n    \"items\": [\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Initial deposit\",\n        \"item_ref\": \"INV-2025-10450\",\n        \"item_type\": \"deposit\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 50000\n        }\n      },\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Installment 1 of 10\",\n        \"item_ref\": \"INV-2025-10451\",\n        \"item_type\": \"installment\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 30000\n        }\n      },\n      {\n        \"item_name\": \"Platform transaction fee\",\n        \"item_ref\": \"INV-2025-10452\",\n        \"item_type\": \"platform_fee\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        }\n      },\n      {\n        \"item_name\": \"Agent commission\",\n        \"item_ref\": \"INV-2025-10453\",\n        \"item_type\": \"commission\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        }\n      }\n    ],\n    \"recipient_details\": [\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 80000\n        },\n        \"payment_reference\": \"BLV-805-DEPOSIT-INS1\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"123456\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        },\n        \"payment_reference\": \"PLATFORM-FEE-AUG\",\n        \"purpose_of_transaction\": \"BILL_PAYMENTS\",\n        \"recipient_id\": \"654321\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        },\n        \"payment_reference\": \"AGENT-COMM-AUG\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"789012\"\n      }\n    ],\n    \"allowed_origins\": [\n      \"https://app.example.com\"\n    ]\n  }\n}")
  .asString();
```

**`Single property with deposit, installment and platform fees`**

```php Single property with deposit, installment and platform fees
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.redpincompany.com/v1/customers/customer_123/sessions', [
  'body' => '{
  "body": {
    "client_customer_ref": "CUST-1002-TXN",
    "client_reference_id": "PAY-2025-08-20-002",
    "amount": {
      "currency": "AED",
      "value": 87500
    },
    "due_date": "2025-08-20",
    "items": [
      {
        "item_name": "Boulevard Residences - Unit 805 - Initial deposit",
        "item_ref": "INV-2025-10450",
        "item_type": "deposit",
        "amount": {
          "currency": "AED",
          "value": 50000
        }
      },
      {
        "item_name": "Boulevard Residences - Unit 805 - Installment 1 of 10",
        "item_ref": "INV-2025-10451",
        "item_type": "installment",
        "amount": {
          "currency": "AED",
          "value": 30000
        }
      },
      {
        "item_name": "Platform transaction fee",
        "item_ref": "INV-2025-10452",
        "item_type": "platform_fee",
        "amount": {
          "currency": "AED",
          "value": 5000
        }
      },
      {
        "item_name": "Agent commission",
        "item_ref": "INV-2025-10453",
        "item_type": "commission",
        "amount": {
          "currency": "AED",
          "value": 2500
        }
      }
    ],
    "recipient_details": [
      {
        "amount": {
          "currency": "AED",
          "value": 80000
        },
        "payment_reference": "BLV-805-DEPOSIT-INS1",
        "purpose_of_transaction": "PROPERTY_PURCHASE",
        "recipient_id": "123456"
      },
      {
        "amount": {
          "currency": "AED",
          "value": 5000
        },
        "payment_reference": "PLATFORM-FEE-AUG",
        "purpose_of_transaction": "BILL_PAYMENTS",
        "recipient_id": "654321"
      },
      {
        "amount": {
          "currency": "AED",
          "value": 2500
        },
        "payment_reference": "AGENT-COMM-AUG",
        "purpose_of_transaction": "PROPERTY_PURCHASE",
        "recipient_id": "789012"
      }
    ],
    "allowed_origins": [
      "https://app.example.com"
    ]
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

**`Single property with deposit, installment and platform fees`**

```csharp Single property with deposit, installment and platform fees
using RestSharp;

var client = new RestClient("https://api.redpincompany.com/v1/customers/customer_123/sessions");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"body\": {\n    \"client_customer_ref\": \"CUST-1002-TXN\",\n    \"client_reference_id\": \"PAY-2025-08-20-002\",\n    \"amount\": {\n      \"currency\": \"AED\",\n      \"value\": 87500\n    },\n    \"due_date\": \"2025-08-20\",\n    \"items\": [\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Initial deposit\",\n        \"item_ref\": \"INV-2025-10450\",\n        \"item_type\": \"deposit\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 50000\n        }\n      },\n      {\n        \"item_name\": \"Boulevard Residences - Unit 805 - Installment 1 of 10\",\n        \"item_ref\": \"INV-2025-10451\",\n        \"item_type\": \"installment\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 30000\n        }\n      },\n      {\n        \"item_name\": \"Platform transaction fee\",\n        \"item_ref\": \"INV-2025-10452\",\n        \"item_type\": \"platform_fee\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        }\n      },\n      {\n        \"item_name\": \"Agent commission\",\n        \"item_ref\": \"INV-2025-10453\",\n        \"item_type\": \"commission\",\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        }\n      }\n    ],\n    \"recipient_details\": [\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 80000\n        },\n        \"payment_reference\": \"BLV-805-DEPOSIT-INS1\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"123456\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 5000\n        },\n        \"payment_reference\": \"PLATFORM-FEE-AUG\",\n        \"purpose_of_transaction\": \"BILL_PAYMENTS\",\n        \"recipient_id\": \"654321\"\n      },\n      {\n        \"amount\": {\n          \"currency\": \"AED\",\n          \"value\": 2500\n        },\n        \"payment_reference\": \"AGENT-COMM-AUG\",\n        \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n        \"recipient_id\": \"789012\"\n      }\n    ],\n    \"allowed_origins\": [\n      \"https://app.example.com\"\n    ]\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

**`Single property with deposit, installment and platform fees`**

```swift Single property with deposit, installment and platform fees
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["body": [
    "client_customer_ref": "CUST-1002-TXN",
    "client_reference_id": "PAY-2025-08-20-002",
    "amount": [
      "currency": "AED",
      "value": 87500
    ],
    "due_date": "2025-08-20",
    "items": [
      [
        "item_name": "Boulevard Residences - Unit 805 - Initial deposit",
        "item_ref": "INV-2025-10450",
        "item_type": "deposit",
        "amount": [
          "currency": "AED",
          "value": 50000
        ]
      ],
      [
        "item_name": "Boulevard Residences - Unit 805 - Installment 1 of 10",
        "item_ref": "INV-2025-10451",
        "item_type": "installment",
        "amount": [
          "currency": "AED",
          "value": 30000
        ]
      ],
      [
        "item_name": "Platform transaction fee",
        "item_ref": "INV-2025-10452",
        "item_type": "platform_fee",
        "amount": [
          "currency": "AED",
          "value": 5000
        ]
      ],
      [
        "item_name": "Agent commission",
        "item_ref": "INV-2025-10453",
        "item_type": "commission",
        "amount": [
          "currency": "AED",
          "value": 2500
        ]
      ]
    ],
    "recipient_details": [
      [
        "amount": [
          "currency": "AED",
          "value": 80000
        ],
        "payment_reference": "BLV-805-DEPOSIT-INS1",
        "purpose_of_transaction": "PROPERTY_PURCHASE",
        "recipient_id": "123456"
      ],
      [
        "amount": [
          "currency": "AED",
          "value": 5000
        ],
        "payment_reference": "PLATFORM-FEE-AUG",
        "purpose_of_transaction": "BILL_PAYMENTS",
        "recipient_id": "654321"
      ],
      [
        "amount": [
          "currency": "AED",
          "value": 2500
        ],
        "payment_reference": "AGENT-COMM-AUG",
        "purpose_of_transaction": "PROPERTY_PURCHASE",
        "recipient_id": "789012"
      ]
    ],
    "allowed_origins": ["https://app.example.com"]
  ]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.redpincompany.com/v1/customers/customer_123/sessions")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

#### API-Integrated Payments

For API-integrated payments, store identifiers immediately after payment creation:

### Request

POST [https://sandbox.currenciesdirect.com/v3/customers/\{customer\_id}/payments](https://sandbox.currenciesdirect.com/v3/customers/\{customer_id}/payments)

**`Single Recipient Third-Party Payment`**

```curl Single Recipient Third-Party Payment
curl -X POST https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "purpose_of_transaction": "PROPERTY_PURCHASE",
  "amount": {
    "currency": "GBP",
    "value": 50000
  },
  "recipient_details": [
    {
      "amount": {
        "currency": "GBP",
        "value": 50000
      },
      "recipient_id": "REC-001",
      "payment_reference": "PROPERTY_PURCHASE_PAYMENT",
      "purpose_of_transaction": "PROPERTY_PURCHASE"
    }
  ],
  "client_reference_id": "REF120532",
  "quote_id": "548eb44b-5e1b-45fb-86eb-88eb5bcaa781",
  "is_third_party_payment": true,
  "client_customer_ref": "CUST-001",
  "payment_reference": "PAY-2024-001"
}'
```

**`Single Recipient Third-Party Payment`**

```python Single Recipient Third-Party Payment
import requests

url = "https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments"

payload = {
    "purpose_of_transaction": "PROPERTY_PURCHASE",
    "amount": {
        "currency": "GBP",
        "value": 50000
    },
    "recipient_details": [
        {
            "amount": {
                "currency": "GBP",
                "value": 50000
            },
            "recipient_id": "REC-001",
            "payment_reference": "PROPERTY_PURCHASE_PAYMENT",
            "purpose_of_transaction": "PROPERTY_PURCHASE"
        }
    ],
    "client_reference_id": "REF120532",
    "quote_id": "548eb44b-5e1b-45fb-86eb-88eb5bcaa781",
    "is_third_party_payment": True,
    "client_customer_ref": "CUST-001",
    "payment_reference": "PAY-2024-001"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

**`Single Recipient Third-Party Payment`**

```javascript Single Recipient Third-Party Payment
const url = 'https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"purpose_of_transaction":"PROPERTY_PURCHASE","amount":{"currency":"GBP","value":50000},"recipient_details":[{"amount":{"currency":"GBP","value":50000},"recipient_id":"REC-001","payment_reference":"PROPERTY_PURCHASE_PAYMENT","purpose_of_transaction":"PROPERTY_PURCHASE"}],"client_reference_id":"REF120532","quote_id":"548eb44b-5e1b-45fb-86eb-88eb5bcaa781","is_third_party_payment":true,"client_customer_ref":"CUST-001","payment_reference":"PAY-2024-001"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

**`Single Recipient Third-Party Payment`**

```go Single Recipient Third-Party Payment
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments"

	payload := strings.NewReader("{\n  \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n  \"amount\": {\n    \"currency\": \"GBP\",\n    \"value\": 50000\n  },\n  \"recipient_details\": [\n    {\n      \"amount\": {\n        \"currency\": \"GBP\",\n        \"value\": 50000\n      },\n      \"recipient_id\": \"REC-001\",\n      \"payment_reference\": \"PROPERTY_PURCHASE_PAYMENT\",\n      \"purpose_of_transaction\": \"PROPERTY_PURCHASE\"\n    }\n  ],\n  \"client_reference_id\": \"REF120532\",\n  \"quote_id\": \"548eb44b-5e1b-45fb-86eb-88eb5bcaa781\",\n  \"is_third_party_payment\": true,\n  \"client_customer_ref\": \"CUST-001\",\n  \"payment_reference\": \"PAY-2024-001\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

**`Single Recipient Third-Party Payment`**

```ruby Single Recipient Third-Party Payment
require 'uri'
require 'net/http'

url = URI("https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n  \"amount\": {\n    \"currency\": \"GBP\",\n    \"value\": 50000\n  },\n  \"recipient_details\": [\n    {\n      \"amount\": {\n        \"currency\": \"GBP\",\n        \"value\": 50000\n      },\n      \"recipient_id\": \"REC-001\",\n      \"payment_reference\": \"PROPERTY_PURCHASE_PAYMENT\",\n      \"purpose_of_transaction\": \"PROPERTY_PURCHASE\"\n    }\n  ],\n  \"client_reference_id\": \"REF120532\",\n  \"quote_id\": \"548eb44b-5e1b-45fb-86eb-88eb5bcaa781\",\n  \"is_third_party_payment\": true,\n  \"client_customer_ref\": \"CUST-001\",\n  \"payment_reference\": \"PAY-2024-001\"\n}"

response = http.request(request)
puts response.read_body
```

**`Single Recipient Third-Party Payment`**

```java Single Recipient Third-Party Payment
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n  \"amount\": {\n    \"currency\": \"GBP\",\n    \"value\": 50000\n  },\n  \"recipient_details\": [\n    {\n      \"amount\": {\n        \"currency\": \"GBP\",\n        \"value\": 50000\n      },\n      \"recipient_id\": \"REC-001\",\n      \"payment_reference\": \"PROPERTY_PURCHASE_PAYMENT\",\n      \"purpose_of_transaction\": \"PROPERTY_PURCHASE\"\n    }\n  ],\n  \"client_reference_id\": \"REF120532\",\n  \"quote_id\": \"548eb44b-5e1b-45fb-86eb-88eb5bcaa781\",\n  \"is_third_party_payment\": true,\n  \"client_customer_ref\": \"CUST-001\",\n  \"payment_reference\": \"PAY-2024-001\"\n}")
  .asString();
```

**`Single Recipient Third-Party Payment`**

```php Single Recipient Third-Party Payment
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments', [
  'body' => '{
  "purpose_of_transaction": "PROPERTY_PURCHASE",
  "amount": {
    "currency": "GBP",
    "value": 50000
  },
  "recipient_details": [
    {
      "amount": {
        "currency": "GBP",
        "value": 50000
      },
      "recipient_id": "REC-001",
      "payment_reference": "PROPERTY_PURCHASE_PAYMENT",
      "purpose_of_transaction": "PROPERTY_PURCHASE"
    }
  ],
  "client_reference_id": "REF120532",
  "quote_id": "548eb44b-5e1b-45fb-86eb-88eb5bcaa781",
  "is_third_party_payment": true,
  "client_customer_ref": "CUST-001",
  "payment_reference": "PAY-2024-001"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

**`Single Recipient Third-Party Payment`**

```csharp Single Recipient Third-Party Payment
using RestSharp;

var client = new RestClient("https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"purpose_of_transaction\": \"PROPERTY_PURCHASE\",\n  \"amount\": {\n    \"currency\": \"GBP\",\n    \"value\": 50000\n  },\n  \"recipient_details\": [\n    {\n      \"amount\": {\n        \"currency\": \"GBP\",\n        \"value\": 50000\n      },\n      \"recipient_id\": \"REC-001\",\n      \"payment_reference\": \"PROPERTY_PURCHASE_PAYMENT\",\n      \"purpose_of_transaction\": \"PROPERTY_PURCHASE\"\n    }\n  ],\n  \"client_reference_id\": \"REF120532\",\n  \"quote_id\": \"548eb44b-5e1b-45fb-86eb-88eb5bcaa781\",\n  \"is_third_party_payment\": true,\n  \"client_customer_ref\": \"CUST-001\",\n  \"payment_reference\": \"PAY-2024-001\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

**`Single Recipient Third-Party Payment`**

```swift Single Recipient Third-Party Payment
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "purpose_of_transaction": "PROPERTY_PURCHASE",
  "amount": [
    "currency": "GBP",
    "value": 50000
  ],
  "recipient_details": [
    [
      "amount": [
        "currency": "GBP",
        "value": 50000
      ],
      "recipient_id": "REC-001",
      "payment_reference": "PROPERTY_PURCHASE_PAYMENT",
      "purpose_of_transaction": "PROPERTY_PURCHASE"
    ]
  ],
  "client_reference_id": "REF120532",
  "quote_id": "548eb44b-5e1b-45fb-86eb-88eb5bcaa781",
  "is_third_party_payment": true,
  "client_customer_ref": "CUST-001",
  "payment_reference": "PAY-2024-001"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://sandbox.currenciesdirect.com/v3/customers/cust_123456789/payments")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

#### Recommended database schema

Store these fields when you create a payment (hosted session or API) to enable webhook matching:

```sql
CREATE TABLE payments (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  session_id VARCHAR(255) UNIQUE NULL, -- NULL for API-integrated payments
  payment_id VARCHAR(255) UNIQUE NULL, -- Set when first webhook arrives
  client_customer_ref VARCHAR(255) NULL, -- NULL for non-third-party payments
  client_reference_id VARCHAR(36) UNIQUE NOT NULL, -- Required for third-party, recommended for all
  payment_type ENUM('HOSTED_SESSION', 'API_THIRD_PARTY', 'API_NON_THIRD_PARTY') NOT NULL,
  session_url TEXT NULL, -- Only for hosted sessions
  expires_at TIMESTAMP NULL, -- Only for hosted sessions
  status VARCHAR(50) DEFAULT 'CREATED',
  amount_currency VARCHAR(3) NOT NULL,
  amount_value DECIMAL(19, 2) NOT NULL,
  purpose_of_transaction VARCHAR(50) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_client_customer_ref (client_customer_ref),
  INDEX idx_client_reference_id (client_reference_id),
  INDEX idx_payment_id (payment_id),
  INDEX idx_session_id (session_id),
  INDEX idx_payment_type_status (payment_type, status, created_at)
);

CREATE TABLE payment_items (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  payment_id BIGINT NOT NULL, -- Reference to payments.id
  session_id VARCHAR(255) NULL, -- For hosted sessions only
  item_name VARCHAR(100) NOT NULL,
  item_ref VARCHAR(50),
  item_type VARCHAR(50),
  amount_currency VARCHAR(3) NOT NULL,
  amount_value DECIMAL(19, 2) NOT NULL,
  FOREIGN KEY (payment_id) REFERENCES payments(id),
  INDEX idx_payment_id (payment_id),
  INDEX idx_session_id (session_id),
  INDEX idx_item_ref (item_ref)
);

CREATE TABLE webhook_events (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  event_id VARCHAR(255) UNIQUE NOT NULL,
  payment_id VARCHAR(255) NOT NULL,
  status VARCHAR(50) NOT NULL,
  payload JSON NOT NULL,
  received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  processed BOOLEAN DEFAULT FALSE,
  INDEX idx_payment_id (payment_id),
  INDEX idx_event_id (event_id)
);
```

> **Note**
>
> **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.

```mermaid
flowchart TD
    %% Style definitions
    classDef clientAction fill:#0D6EFD,stroke:#0B5ED7,color:#fff
    classDef redpinEvent fill:#10B981,stroke:#059669,color:#fff
    classDef errorEvent fill:#EF4444,stroke:#DC2626,color:#fff
    classDef dataStore fill:#6366F1,stroke:#4F46E5,color:#fff
    classDef decision fill:#F59E0B,stroke:#D97706,color:#fff

    %% Client initiates session
    START([Client: Create Payment Session]):::clientAction
    START --> API[POST /v2/customers/.../sessions<br />with client_customer_ref,<br />client_reference_id, items]:::clientAction

    API --> STORE_SESSION[Store in DB:<br />session_id to client_reference_id<br />session_id to items<br />status = CREATED]:::dataStore

    STORE_SESSION --> REDIRECT[Redirect Customer<br />to session_url]:::clientAction

    %% Customer completes payment
    REDIRECT --> AWAITING[Webhook: AWAITING_FUNDS<br />payment_id, session_id,<br />client_customer_ref]:::redpinEvent

    AWAITING --> MATCH1{Match<br />payment_id to<br />session?}:::decision
    MATCH1 -->|Yes| UPDATE1[Update DB:<br />payment_id to session_id<br />status = AWAITING_FUNDS]:::dataStore
    MATCH1 -->|No| LOG1[Log Unmatched Event<br />Alert Operations]:::errorEvent

    %% Happy path statuses
    UPDATE1 --> RECEIVED[Webhook: RECEIVED_FUNDS<br />payment_id, amount]:::redpinEvent
    RECEIVED --> UPDATE2[Update DB:<br />status = RECEIVED_FUNDS<br />store amount received]:::dataStore

    UPDATE2 --> FX[Webhook: FX_COMPLETED<br />payment_id, sell/buy amounts,<br />quote_rate]:::redpinEvent
    FX --> UPDATE3[Update DB:<br />status = FX_COMPLETED<br />store conversion details]:::dataStore

    UPDATE3 --> PAYOUT_INIT[Webhook: PAYOUT_INITIATED<br />payment_id, recipient_id,<br />amount]:::redpinEvent
    PAYOUT_INIT --> UPDATE4[Update DB:<br />status = PAYOUT_INITIATED<br />track recipient_id]:::dataStore

    UPDATE4 --> PAYOUT_CRED[Webhook: PAYOUT_CREDITED<br />payment_id, recipient_id,<br />amount]:::redpinEvent
    PAYOUT_CRED --> CHECK_MULTI{Multiple<br />recipients?}:::decision

    CHECK_MULTI -->|Yes| UPDATE5A[Update DB:<br />Mark recipient_id as PAID<br />Check if all recipients paid]:::dataStore
    CHECK_MULTI -->|No| UPDATE5B[Update DB:<br />status = PAYOUT_CREDITED]:::dataStore

    UPDATE5A --> WAIT_COMPLETE[Wait for<br />PAYMENT_COMPLETED]:::clientAction
    UPDATE5B --> PAYMENT_DONE[Webhook: PAYMENT_COMPLETED<br />payment_id]:::redpinEvent
    WAIT_COMPLETE --> PAYMENT_DONE

    PAYMENT_DONE --> FINAL[Update DB:<br />status = PAYMENT_COMPLETED<br />Mark as fully reconciled]:::dataStore
    FINAL --> END([Reconciliation Complete]):::clientAction
    %% Error paths
    RECEIVED -.->|Error Path| CANCELLED[Webhook: CANCELLED<br />payment_id]:::errorEvent
    PAYOUT_INIT -.->|Error Path| BOUNCED[Webhook: BOUNCED_BACK<br />payment_id]:::errorEvent

    CANCELLED --> ERROR_HANDLE1[Update DB:<br />status = CANCELLED<br />Refund/retry logic]:::dataStore
    BOUNCED --> ERROR_HANDLE3[Update DB:<br />status = BOUNCED_BACK<br />Investigate & retry]:::dataStore

    ERROR_HANDLE1 --> END_ERROR([Manual Intervention]):::errorEvent
    ERROR_HANDLE3 --> END_ERROR
```

#### 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 (v1)
* If create times out or your client retries, call `GET /v1/customers/{customer_id}/sessions?client_reference_id={ref}` first to fetch an existing session and avoid duplicate creation
* Store `session_id`, `client_reference_id`, `client_customer_ref`, and all items immediately
* Each item can have an `item_ref` for granular reconciliation (invoice numbers, property IDs, booking references, etc.)
* Redirect customer to the `session_url` to complete payment

**2. First Webhook (AWAITING\_FUNDS or PROCESSING)**

* **FX payments:** AWAITING\_FUNDS — contains `payment_id` for the first time, along with `client_reference_id`. Match to your session using `client_reference_id`, map `payment_id` to `session_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_id` and `client_customer_ref`. Map `payment_id` to 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_id` status 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
* 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**:

1. Match the webhook to your session using `client_reference_id`
2. Store the `payment_id` and link it to your `session_id`
3. Update status to AWAITING\_FUNDS
4. Display "Payment initiated, awaiting funds" to customer

```json
{
  "event_id": "evt_1234567890",
  "payment_id": "pay_abcdef123456",
  "status": "AWAITING_FUNDS",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:30:00Z",
  "data": {}
}
```

#### 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**:

1. Match the webhook to your payment record using `client_reference_id` or `payment_id`
2. Store or link the `payment_id` to your record
3. Update status to PROCESSING
4. Expect next webhooks: PAYOUT\_INITIATED → PAYOUT\_CREDITED → PAYMENT\_COMPLETED

```json
{
  "event_id": "evt_1234567890",
  "payment_id": "pay_abcdef123456",
  "status": "PROCESSING",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:30:00Z",
  "data": {}
}
```

#### RECEIVED\_FUNDS

**When**: Customer's funds received in Redpin wallet

**Webhook contains**:

* `payment_id`
* `data.amount.currency`: Currency of funds received
* `data.amount.value`: Amount received

**Action**:

1. Find payment by `payment_id` in your database
2. Update status to RECEIVED\_FUNDS
3. Store amount received for reconciliation
4. Display "Funds received, processing conversion" to customer

```json
{
  "event_id": "evt_1234567891",
  "payment_id": "pay_abcdef123456",
  "status": "RECEIVED_FUNDS",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:35:00Z",
  "data": {
    "amount": {
      "currency": "GBP",
      "value": 1000.00
    }
  }
}
```

#### FX\_COMPLETED

**When**: Currency conversion completed

**Webhook contains**:

* `payment_id`
* `data.sell_amount`: Source currency and amount
* `data.buy_amount`: Destination currency and amount
* `data.quote_rate`: Exchange rate used

**Action**:

1. Find payment by `payment_id`
2. Update status to FX\_COMPLETED
3. Store conversion details for audit trail
4. Display "Conversion complete, initiating payout" to customer

```json
{
  "event_id": "evt_1234567892",
  "payment_id": "pay_abcdef123456",
  "status": "FX_COMPLETED",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:36:00Z",
  "data": {
    "sell_amount": {
      "currency": "GBP",
      "value": 1000.00
    },
    "buy_amount": {
      "currency": "AED",
      "value": 4982.70
    },
    "quote_rate": 4.9827
  }
}
```

#### PAYOUT\_INITIATED

**When**: Transfer to recipient started

**Webhook contains**:

* `payment_id`
* `data.amount`: Amount being sent to recipient
* `data.recipient_id`: Unique identifier for the recipient

**Action**:

1. Find payment by `payment_id`
2. Update status to PAYOUT\_INITIATED
3. Track `recipient_id` (especially important for multi-recipient payments)
4. Display "Payout initiated to recipient" to customer

```json
{
  "event_id": "evt_1234567893",
  "payment_id": "pay_abcdef123456",
  "status": "PAYOUT_INITIATED",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:40:00Z",
  "data": {
    "amount": {
      "currency": "AED",
      "value": 4982.70
    },
    "recipient_id": "162345"
  }
}
```

#### PAYOUT\_CREDITED

**When**: Recipient's bank account credited

**Webhook contains**:

* `payment_id`
* `data.amount`: Amount credited to recipient
* `data.recipient_id`: Unique identifier for the recipient

**Action**:

1. Find payment by `payment_id`
2. Mark this specific `recipient_id` as PAID
3. If multiple recipients, check if all are paid
4. If single recipient, wait for PAYMENT\_COMPLETED
5. Display "Funds delivered to recipient" to customer

```json
{
  "event_id": "evt_1234567894",
  "payment_id": "pay_abcdef123456",
  "status": "PAYOUT_CREDITED",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:45:00Z",
  "data": {
    "amount": {
      "currency": "AED",
      "value": 4982.70
    },
    "recipient_id": "162345"
  }
}
```

#### PAYMENT\_COMPLETED

**When**: All recipients paid, payment fully complete

**Webhook contains**:

* `payment_id`
* `data.recipient_details`: Array of all recipients with amounts and IDs

**Action**:

1. Find payment by `payment_id`
2. Update status to PAYMENT\_COMPLETED
3. Mark as fully reconciled in your system
4. Display "Payment complete" to customer
5. Trigger post-payment workflows (accounting, notifications)

```json
{
  "event_id": "evt_1234567895",
  "payment_id": "pay_abcdef123456",
  "status": "PAYMENT_COMPLETED",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:46:00Z",
  "data": {
    "recipient_details": [
      {
        "amount": {
          "currency": "AED",
          "value": 4982.70
        },
        "recipient_id": "162345"
      }
    ]
  }
}
```

> **Warning**
>
> 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:

1. PAYOUT\_INITIATED fires once per recipient (with `recipient_id`)
2. PAYOUT\_CREDITED fires once per recipient (with `recipient_id`)
3. PAYMENT\_COMPLETED fires once when all recipients are paid

#### Multi-recipient tracking logic

Maintain a separate table to track individual recipient status:

```sql
CREATE TABLE payment_recipients (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  payment_id VARCHAR(255) NOT NULL,
  recipient_id VARCHAR(255) NOT NULL,
  amount_currency VARCHAR(3) NOT NULL,
  amount_value DECIMAL(19, 2) NOT NULL,
  status VARCHAR(50) DEFAULT 'PENDING',
  payout_initiated_at TIMESTAMP,
  payout_credited_at TIMESTAMP,
  UNIQUE KEY (payment_id, recipient_id),
  INDEX idx_payment_id (payment_id)
);
```

Example reconciliation logic:

```typescript
async function handlePayoutCredited(webhook: PayoutCreditedEvent) {
  // Update recipient status
  await db.payment_recipients.update({
    where: {
      payment_id: webhook.payment_id,
      recipient_id: webhook.data.recipient_id
    },
    data: {
      status: 'CREDITED',
      payout_credited_at: webhook.event_timestamp
    }
  });

  // Check if all recipients for this payment are credited
  const allRecipients = await db.payment_recipients.findMany({
    where: { payment_id: webhook.payment_id }
  });

  const allCredited = allRecipients.every(r => r.status === 'CREDITED');

  if (allCredited) {
    // Still wait for PAYMENT_COMPLETED webhook for final confirmation
    console.log('All recipients credited, awaiting PAYMENT_COMPLETED');
  }
}
```

> **Tip**
>
> 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_id`

**Action**:

1. Update status to CANCELLED
2. Process refund logic if applicable
3. Notify customer of cancellation

```json
{
  "event_id": "evt_1234567896",
  "payment_id": "pay_abcdef123456",
  "status": "CANCELLED",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T10:50:00Z",
  "data": {}
}
```

#### BOUNCED\_BACK status

**When**: Payout failed and funds returned

**Webhook contains**:

* `payment_id`
* `data.amount`: Amount that bounced
* `data.recipient_id`: Affected recipient

**Action**:

1. Update status to BOUNCED\_BACK
2. Store affected recipient info
3. Investigate the cause (invalid account, incorrect details)
4. Contact support if needed
5. Prepare retry with corrected details

```json
{
  "event_id": "evt_1234567898",
  "payment_id": "pay_abcdef123456",
  "status": "BOUNCED_BACK",
  "customer_id": "0201001008132685",
  "client_customer_ref": "CUST-1001-TXN",
  "client_reference_id": "PAY-2025-08-15-001",
  "event_timestamp": "2025-12-02T11:10:00Z",
  "data": {
    "amount": {
      "currency": "AED",
      "value": 4982.70
    },
    "recipient_id": "162345"
  }
}
```

#### Unmatched webhooks

**When**: Webhook arrives but cannot match to any session

**Possible causes**:

* Session not stored before webhook arrives
* `client_customer_ref` mismatch
* Database sync lag

**Action**:

1. Log the unmatched webhook with full payload
2. Alert operations team immediately
3. Investigate root cause (timing issue, data mismatch, system error)
4. 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**:

1. Use `event_timestamp` to determine actual event order
2. Handle webhooks idempotently (processing same webhook twice should be safe)
3. Do not assume strict ordering in your logic
4. 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**:

1. Check `event_id` before processing any webhook
2. If `event_id` already exists in your webhook\_events table, skip processing
3. Return 200 OK to acknowledge receipt (prevents further retries)
4. Log duplicate detection for monitoring

```typescript
async function processWebhook(webhook: WebhookPayload) {
  // Check for duplicate
  const existing = await db.webhook_events.findOne({
    where: { event_id: webhook.event_id }
  });

  if (existing) {
    console.log(`Duplicate webhook ${webhook.event_id}, skipping`);
    return { status: 200, message: 'Already processed' };
  }

  // Process webhook...
  await db.webhook_events.create({
    event_id: webhook.event_id,
    payment_id: webhook.payment_id,
    status: webhook.status,
    payload: webhook,
    processed: true
  });
}
```

> **Error**
>
> 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.

> **Note**
>
> Webhooks use Svix for delivery. See the [Webhooks overview](/api-guide/webhooks) for signature verification implementation.

## Best practices

Follow these best practices for reliable payment reconciliation:

> **Check**
>
> **Use `client_reference_id` as primary reconciliation key**
>
> This is your unique payment reference (max 36 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.
>
> For hosted sessions, use `GET /v1/customers/{customer_id}/sessions?client_reference_id={ref}` as your first recovery step when session creation is retried or the original create response is lost.

### Session lookup decisioning (retry or lost create response)

Use this decision flow immediately after calling `GET /v1/customers/{customer_id}/sessions?client_reference_id={ref}`:

```mermaid
flowchart TD
    retryEvent[RetryOrLostCreateResponse] --> lookupByRef[LookupByCustomerAndClientReferenceId]
    lookupByRef --> sessionFound{SessionFound}
    sessionFound -->|No| createNewSession[CreateNewSession]
    sessionFound -->|Yes| paymentPresent{HasPayment}
    paymentPresent -->|Yes| trackByPayment[TrackByPaymentIdAndWebhooks]
    paymentPresent -->|No| sessionExpired{SessionExpired}
    sessionExpired -->|No| reuseSession[ReuseExistingSession]
    sessionExpired -->|Yes| createReplacement[CreateReplacementSession]
```

* If `sessions` is empty, create a new session.
* If `sessions[0].has_payment` is `true` and `payment_id` is present, do not create another session; continue reconciliation using `payment_id` and webhook events.
* If `sessions[0].latest_status` is `SESSION_CREATED` and `session_expires_at` is still in the future, reuse that session.
* If `session_expires_at` is in the past and `has_payment` is `false`, create a replacement session according to your idempotency/session retry policy.
* Always query and match using the pair `customer_id` + `client_reference_id`; do not reconcile by `client_reference_id` alone.

> **Check**
>
> **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.

> **Check**
>
> **Implement webhook signature verification**
>
> Validate the authenticity of webhooks to prevent fraudulent requests. See the [Webhooks overview](/api-guide/webhooks) for implementation details using Svix signatures.

> **Check**
>
> **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.

> **Check**
>
> **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
> * Alert when repeated retries return the same `SESSION_CREATED` session near `session_expires_at` and `has_payment` remains `false`

> **Check**
>
> **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.

> **Check**
>
> **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.

> **Check**
>
> **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**:

1. AWAITING\_FUNDS
2. RECEIVED\_FUNDS
3. FX\_COMPLETED
4. PAYOUT\_INITIATED
5. PAYOUT\_CREDITED
6. PAYMENT\_COMPLETED

**Validation**:

* Verify each status transition is stored correctly
* Confirm `payment_id` mapped to `session_id` on first webhook
* Check all amounts and conversion rates stored accurately

#### Test 2: Multiple recipients success flow

**Setup**: Create session with 3 recipients

**Expected webhooks**:

1. AWAITING\_FUNDS
2. RECEIVED\_FUNDS
3. FX\_COMPLETED
4. PAYOUT\_INITIATED (recipient 1)
5. PAYOUT\_INITIATED (recipient 2)
6. PAYOUT\_INITIATED (recipient 3)
7. PAYOUT\_CREDITED (recipient 1)
8. PAYOUT\_CREDITED (recipient 2)
9. PAYOUT\_CREDITED (recipient 3)
10. PAYMENT\_COMPLETED

**Validation**:

* Verify PAYOUT\_CREDITED fires 3 times (once per recipient)
* Confirm each `recipient_id` tracked separately
* Check PAYMENT\_COMPLETED fires only after all recipients paid

#### Test 3: Payment cancellation

**Setup**: Create session but cancel before completion

**Expected webhooks**:

1. AWAITING\_FUNDS
2. CANCELLED

**Validation**:

* Verify cancellation processed correctly
* 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_id` deduplication works
* Confirm no duplicate status updates in database
* Check idempotency maintained

> **Note**
>
> 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**:

1. Verify webhook URL returns 200 OK when called
2. Check webhook endpoint logs for rejected requests
3. Review signature verification implementation
4. Ensure URL uses HTTPS (HTTP may be blocked)
5. Check firewall rules allow Svix IP ranges
6. 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_id` mismatch between session and webhook
* Session not stored before webhook arrives (timing issue)
* Database sync lag in replicated environment

**Solutions**:

1. Log `client_reference_id` from webhook and compare to database records
2. Run `GET /v1/customers/{customer_id}/sessions?client_reference_id={ref}` to retrieve the matching session record and recover `session_id`
3. Ensure session stored synchronously before redirecting customer
4. Check database replication lag if using read replicas
5. Verify `client_reference_id` format consistency (trimming, case sensitivity)
6. 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**:

1. Check if customer completed session flow (check session expiration)
2. Contact Partner Integrations Team with `payment_id` and `session_id`
3. Review webhook logs for missed deliveries
4. Check customer communication (email bounces, failed notifications)
5. 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**:

1. Wait up to 5 minutes after last PAYOUT\_CREDITED webhook
2. Verify all expected recipients received PAYOUT\_CREDITED
3. Check webhook delivery logs if still missing after 5 minutes
4. Contact support if issue persists beyond 10 minutes
5. 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_id` for idempotency
* Webhook processing not transactional
* Race condition in concurrent webhook processing

**Solutions**:

1. Implement `event_id` deduplication check before processing
2. Use database transactions for webhook processing
3. Add unique constraint on `event_id` in webhook\_events table
4. Implement distributed locks for concurrent webhook processing
5. Return 200 OK even for duplicate events (prevents further retries)

> **Warning**
>
> If payment stuck for more than 24 hours, contact the Partner Integrations Team immediately at [apisupport@redpincompany.com](mailto: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:

#### [Hosted API reference](/api-reference/hosted-experience)

Complete API specification for sessions

#### [Webhook reference](/api-reference/webhooks)

Detailed webhook payload schemas

#### [Integration guidelines](/api-guide/partners/integration-guidelines)

Hosted vs API integration approaches

#### [Error handling](/api-guide/getting-started/error-handling)

Comprehensive error handling patterns

For integration support or questions about reconciliation, contact the Partner Integrations Team at [apisupport@redpincompany.com](mailto:apisupport@redpincompany.com).