Transfer API
Initiate a transaction
Initiate a transfer of money (payment) to a beneficiary via a mobile money or banking service in a supported country.
Method & URL
Payload
| Field | Type | Mandatory | Description |
|---|---|---|---|
| paymentMethod | string | Yes | Payment method: "mobile_money" or "bank" |
| amount | number | Yes | Transfer amount (currency specified when creating the merchant account) |
| description | string | No | Transfer description |
| externalId | string (UUID v4) | Yes | Unique identifier for your transfer. Guarantees idempotency. |
| country | string | Yes | Beneficiary's country |
| walletNumber | string | Conditional | Beneficiary's mobile money number in international format — country dialling code then local number, without + or spaces (e.g. 237690000000). Required if paymentMethod = "mobile_money". |
| provider | string | Conditional | The idantitiCode obtained from the /providers endpoint |
| bankAccount | object | Conditional | Bank account information (required if paymentMethod = "bank") |
| callbackUrl | string | Yes | Callback URL to receive the transaction response |
| recipient | object | Yes | Information about the beneficiary |
| sender | object | No | Information about the sender |
externalId: idempotency key
externalId is a UUID v4 that you generate before sending the request. It is your unique reference for the transfer and makes retries safe.
- Safe retries — if an error occurs, resend the exact same request with the same
externalId. If the transfer already exists, SolFi returns the existing transaction instead of creating a new one. - Search by your reference —
GET /transactions/{externalId}retrieves a transaction using your own reference. - Protection against reuse — reusing an
externalIdwith different parameters is rejected with a409error.
Rules
Never generate a new externalId when retrying — this disables idempotency protection and can create a duplicate transfer.
recipient object (beneficiary)
| Field | Type | Mandatory | Description |
|---|---|---|---|
| firstName | string | Yes | First name |
| lastName | string | Yes | Last name |
| middleName | string | No | Middle name |
| idNumber | string | No | Identity document number |
| idType | string | No | Type of ID |
| birthDate | string (dd/MM/yyyy) | No | Date of birth |
| city | string | No | City |
| address | string | No | Address |
sender object (optional)
| Field | Type | Mandatory | Description |
|---|---|---|---|
| firstName | string | Yes | First name |
| lastName | string | Yes | Last name |
| middleName | string | No | Middle name |
| idNumber | string | No | Identity document number |
| idType | string | No | Type of ID |
| birthDate | string (dd/MM/yyyy) | No | Date of birth |
| city | string | No | City |
| address | string | No | Address |
| phone | string | No | Phone number |
bankAccount object
| Field | Type | Mandatory | Description |
|---|---|---|---|
| iden | string | Yes | Account number or IBAN |
| type | string | Yes | Account type: "ACCOUNT" or "IBAN" |
The bankAccount object is required only when paymentMethod = "bank". For mobile_money, do not include it.
Sample request
curl -X POST "https://api.katika-bridge.com/transactions/transfer" \
-H "x-api-key: your_api_key_here" \
-H "x-api-secret: your_api_secret_here" \
-H "Content-Type: application/json" \
-d '{
"paymentMethod": "mobile_money",
"amount": 10000,
"description": "January salary payment",
"externalId": "550e8400-e29b-41d4-a716-446655440000",
"country": "CM",
"walletNumber": "237690000000",
"provider": "Orange_CM",
"callbackUrl": "https://example.com/callback",
"recipient": {
"firstName": "John",
"lastName": "Doe",
"idNumber": "CI123456789",
"idType": "CNI",
"birthDate": "15/01/1990",
"city": "Douala",
"address": "Rue des Palmiers, Bonanjo"
}
}'Success response (200 OK)
{
"error": false,
"statusCode": 200,
"message": "Transfer initiated successfully",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"reference": "KTK-TR-882910",
"externalId": "550e8400-e29b-41d4-a716-446655440000",
"amount": 10000,
"feeAmount": 150,
"flatFeeAmount": 50,
"variableFeeAmount": 100,
"variableFeePercent": 1,
"totalAmount": 10150,
"currency": "XAF",
"status": "PENDING",
"description": "January salary payment",
"paymentMethod": "mobile_money",
"country": "CM",
"walletNumber": "237690000000",
"provider": "Orange_CM",
"bankAccount": null,
"callbackUrl": "https://example.com/callback",
"recipientName": "John Doe",
"createdAt": "2026-02-02T17:04:09.588Z",
"updatedAt": "2026-02-02T17:04:09.588Z"
}
}feeAmount (total fees) breaks down into flatFeeAmount and variableFeeAmount (computed with variableFeePercent). totalAmount equals amount + feeAmount.
Possible errors
| HTTP code | Description |
|---|---|
| 400 | Invalid request (missing or malformed parameters) |
| 400 / 404 | Authentication error — see Authentication |
| 409 | Conflict: the same externalId was already used with different parameters, or an identical transfer (same amount, beneficiary and provider) was submitted within the last 60 seconds. |
| 500 | Internal server error. It does not necessarily mean the transfer failed — see Error handling & retries. |
Transaction statuses
| Status | Description |
|---|---|
| INITIATED | The transaction has been created in the system (internal status) |
| PENDING | Transaction pending processing |
| SUCCESS | Transaction completed successfully |
| FAILED | Transaction failed (error during processing) |
Internally the transaction is first created as INITIATED, but PENDING is what is returned to the merchant in the immediate response.