Skip to main content

Initiate Transfer

Send money to an existing transfer recipient through bank transfer, mobile money, or other supported channels. Recipients must be created before initiating transfers.
Transfers are irreversible! Always verify recipient details and transfer amounts before initiating. Failed transfers may take 1-5 business days to reverse depending on the channel.

Endpoint

POST /transfer/initiate

Headers

NameTypeRequiredDescription
AuthorizationstringRequiredBearer token with your secret API key
Content-TypestringRequiredMust be application/json
Idempotency-KeystringRecommendedUnique key to prevent duplicate transfers

Request Body

source
string
default:"balance"
Source of funds for the transferAvailable sources:
  • balance - Transfer from your PayVessel wallet balance
  • card - Charge a saved card authorization (coming soon)
amount
string
required
Transfer amount in the smallest currency unit (kobo for NGN, pesewas for GHS, etc.)
For ₦1,000.00, send "100000" (1000 × 100 kobo)
recipient
string
required
Recipient code from the Create Recipient endpoint response
reason
string
Reason for the transfer (optional but recommended for compliance)Common reasons: salary, bonus, refund, payment, gift, loan_repayment, business_payment
reference
string
Unique transfer reference. If not provided, PayVessel will generate one automatically
Must be unique across all your transfers
currency
string
Transfer currency (must match recipient’s currency)Supported currencies: NGN, GHS, KES, ZAR, USD, GBP, EUR
narration
string
Description that appears on the recipient’s bank statement (max 100 characters)
metadata
object
Additional information about the transfer in key-value pairs
{
  "order_id": "ORD_2024_001",
  "customer_id": "12345",
  "invoice_number": "INV-001",
  "department": "HR"
}

Example Request

curl -X POST https://api.payvessel.com/transfer/initiate \
  -H "Authorization: Bearer sk_test_abc123..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: transfer_2024_001" \
  -d '{
    "source": "balance",
    "amount": "100000",
    "recipient": "RCP_xyz789abc123",
    "reason": "salary",
    "reference": "TRF_2024_001",
    "currency": "NGN",
    "narration": "January 2024 Salary",
    "metadata": {
      "employee_id": "EMP001",
      "payroll_batch": "JAN2024",
      "department": "Engineering"
    }
  }'

Response

status
string
Request status indicator - "success" or "error"
message
string
Human-readable message describing the result
data
object
Transfer data object

Example Response

{
  "status": "success",
  "message": "Transfer initiated successfully",
  "data": {
    "id": 567890,
    "reference": "TRF_2024_001",
    "amount": 100000,
    "currency": "NGN",
    "source": "balance",
    "reason": "salary",
    "status": "success",
    "transfer_code": "TRF_xyz789abc123",
    "titan_code": "TITAN_001234567",
    "recipient": {
      "recipient_code": "RCP_xyz789abc123",
      "type": "nuban",
      "name": "John Doe",
      "account_number": "0123456789",
      "bank_name": "Guaranty Trust Bank",
      "bank_code": "058"
    },
    "session": null,
    "fees": 2650,
    "fees_breakdown": {
      "payvessel_fee": 2500,
      "bank_fee": 150,
      "total": 2650
    },
    "narration": "January 2024 Salary",
    "metadata": {
      "employee_id": "EMP001",
      "payroll_batch": "JAN2024",
      "department": "Engineering"
    },
    "created_at": "2024-01-15T14:30:00Z",
    "updated_at": "2024-01-15T14:30:05Z"
  }
}

Transfer Status Guide

Understanding transfer statuses is crucial for proper transfer management:
Transfer ProcessingThe transfer has been initiated and is being processed by the bank or payment provider.What to expect:
  • Processing time: 0-30 minutes for most transfers
  • Bank transfers: Can take up to 24 hours
  • Mobile money: Usually instant to 15 minutes
Next steps:
  • Monitor status via webhooks or verification endpoint
  • No action required from your side
OTP RequiredTransfer requires OTP verification to complete (usually for high-value transfers).What to do:
  • Use the Complete OTP Transfer endpoint
  • OTP will be sent to your registered phone number
  • OTP expires in 10 minutes
Requirements:
  • Transfer amount > ₦100,000
  • First-time recipient transfers
  • Flagged by fraud detection
Transfer CompletedThe transfer has been successfully processed and funds delivered to the recipient.Confirmation:
  • Funds debited from your balance
  • Recipient has received the money
  • Transfer is final and cannot be reversed
Timeline:
  • Instant transfers: Immediate
  • Regular bank transfers: 0-30 minutes
  • Inter-bank transfers: Up to 24 hours
Transfer FailedThe transfer could not be completed. Common reasons include:
  • Invalid recipient account
  • Recipient account issues
  • Network timeout
  • Compliance restrictions
What happens:
  • Funds are automatically refunded to your balance
  • Refund typically takes 5-10 minutes
  • Check gateway_response for specific failure reason
Transfer ReversedA previously successful transfer has been reversed by the bank or payment provider.Common causes:
  • Recipient account closed
  • Fraud detection
  • Bank reversal
  • Compliance issues
Resolution:
  • Funds automatically refunded to your balance
  • Contact support for detailed reversal reason

Fee Structure

PayVessel charges competitive fees for transfers based on amount and destination:
Amount RangeFee
₦100 - ₦5,000₦10
₦5,001 - ₦50,000₦25
₦50,001 - ₦100,000₦50
Above ₦100,000₦100
Additional charges:
  • Bank stamp duty: ₦50 (for transfers > ₦10,000)
  • VAT: 7.5% of PayVessel fee

Integration Patterns

// Process monthly salary payments
const processSalaryPayments = async (employees) => {
  const results = {
    successful: [],
    failed: [],
    pending: []
  };
  
  for (const employee of employees) {
    try {
      const transfer = await payvessel.transfer.initiate({
        source: 'balance',
        amount: employee.salary_amount,
        recipient: employee.recipient_code,
        reason: 'salary',
        reference: `SAL_${employee.id}_${new Date().getMonth() + 1}_2024`,
        narration: `${new Date().toLocaleString('default', { month: 'long' })} 2024 Salary`,
        metadata: {
          employee_id: employee.id,
          payroll_batch: `${new Date().getMonth() + 1}_2024`,
          department: employee.department
        }
      });
      
      if (transfer.data.status === 'success') {
        results.successful.push(transfer.data);
      } else {
        results.pending.push(transfer.data);
      }
    } catch (error) {
      results.failed.push({
        employee_id: employee.id,
        error: error.message
      });
    }
  }
  
  return results;
};

Best Practices

Check Balance First

Always verify sufficient balance before initiating transfers to avoid failures

Use Idempotency Keys

Implement idempotency keys to prevent accidental duplicate transfers

Handle OTP Flows

Implement proper OTP handling for high-value transfers

Monitor Status

Use webhooks or verification endpoints to track transfer status

Meaningful References

Use descriptive, unique references for easier tracking and reconciliation

Store Metadata

Include relevant metadata for audit trails and reporting

Error Handling

Error Code: insufficient_balanceSolution:
  • Check your wallet balance using the Get Balance endpoint
  • Fund your wallet before retrying the transfer
  • Consider the fees in your balance calculation
if (error.code === 'insufficient_balance') {
  const shortage = error.details.shortage;
  console.log(`Need additional ₦${shortage / 100} to complete transfer`);
}
Error Code: invalid_recipientSolution:
  • Verify the recipient code exists and is active
  • Check if recipient account is still valid
  • Recreate recipient if account details changed
if (error.code === 'invalid_recipient') {
  // Verify and potentially recreate recipient
  await verifyRecipient(recipientCode);
}
Error Code: transfer_limit_exceededSolution:
  • Check your account transfer limits
  • Consider breaking large transfers into smaller amounts
  • Contact support to increase limits if needed
if (error.code === 'transfer_limit_exceeded') {
  const limit = error.details.daily_limit;
  console.log(`Daily transfer limit: ₦${limit / 100}`);
}

Next Steps

After initiating a transfer:

Webhook Events

This endpoint triggers the following webhook events:
  • transfer.pending - Transfer initiated and processing
  • transfer.success - Transfer completed successfully
  • transfer.failed - Transfer failed
  • transfer.reversed - Transfer was reversed
Real-time Updates: Use webhooks to get real-time transfer status updates instead of polling the verification endpoint repeatedly.