Create Withdrawal

Overview

Initiate a withdrawal (payout) from your SyncPay balance to an external destination. Use this endpoint to:

  • Send funds to bank accounts
  • Send funds to mobile money wallets
  • Send funds to crypto wallets
  • Cash out your earnings

Important: You must first create a Payout Destination before you can withdraw funds.


Authentication

Type: API Key (required)

Required Headers

Header Required Description
Authorization Yes Format: Bearer sk_test_... or Bearer sk_live_...
Content-Type Yes Must be application/json

Request

Method & Path

POST /api/v1/payouts/withdrawals

Request Body

{
  "destination_id": "dest_1a2b3c4d5e6f",
  "amount": "100.00",
  "currency": "NGN",
  "reference": "WD-12345",
  "idempotency_key": "unique-key-12345"
}

Request Fields

Field Type Required Description
destination_id string Yes ID of payout destination (from Create Payout Destination)
amount string Yes Amount to withdraw (decimal string)
currency string Yes Currency code (must match destination currency)
reference string Yes Your unique reference for this withdrawal
idempotency_key string No Prevents duplicate withdrawals (recommended)

Dependencies & Prerequisites

Before creating a withdrawal:

  1. Check balance: Use Get Balances to ensure sufficient funds
  2. Create destination: Use Create Payout Destination to add withdrawal destination
  3. Verify destination: Optionally use Resolve Bank Account for bank transfers

Links:


Example Use Case

Scenario: Your business needs to transfer funds from your SyncPay balance to your business bank account for operational expenses. You have ₦100,000 in available balance and want to withdraw it to your GTBank account that you've previously configured as a payout destination. The system should verify sufficient balance, process the withdrawal, and provide tracking information.

Implementation:

// Step 1: Check balance
const balances = await fetch('https://api.usesyncpay.com/api/v1/accounts/balances', {
  headers: { 'Authorization': 'Bearer sk_live_abc123xyz...' }
}).then(r => r.json());

const ngnBalance = balances.balances.find(b => b.currency === 'NGN');
console.log(`Available: ₦${ngnBalance.available_balance}`);

// Step 2: Get destination ID (saved from earlier)
const destinationId = 'dest_1a2b3c4d5e6f';

// Step 3: Create withdrawal
const response = await fetch('https://api.usesyncpay.com/api/v1/payouts/withdrawals', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_abc123xyz...',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    destination_id: destinationId,
    amount: '100000.00',
    currency: 'NGN',
    reference: `WD-${Date.now()}`,
    idempotency_key: `withdrawal-${Date.now()}`
  })
});

const withdrawal = await response.json();
console.log('Withdrawal created:', withdrawal.withdrawal_id);
console.log('Status:', withdrawal.status);

Response

200 – Success

Returns the created withdrawal details.

{
  "withdrawal_id": "wd_1a2b3c4d5e6f",
  "status": "PENDING",
  "provider": null,
  "provider_reference": "PAY-12345"
}

Response Fields

Field Type Description
withdrawal_id string Unique withdrawal identifier
status string Withdrawal status (see statuses below)
provider string|null Payment provider (not exposed for security)
provider_reference string Transaction reference ID

Withdrawal Statuses

Status Description Is Final?
PENDING Withdrawal created, queued for processing No
PROCESSING Being processed by payment provider No
COMPLETED Successfully sent to destination Yes
FAILED Withdrawal failed Yes
CANCELLED Cancelled before processing Yes

400 – Bad Request

Validation errors or insufficient funds.

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Insufficient balance",
    "details": {
      "available_balance": "50000.00",
      "requested_amount": "100000.00"
    }
  }
}

Common causes:

  • Insufficient available balance
  • Amount below minimum withdrawal limit
  • Invalid destination ID
  • Currency mismatch (destination currency ≠ withdrawal currency)
  • Invalid reference format
  • Amount format error (must be string, not number)

401 – Unauthorized

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key"
  }
}

404 – Not Found

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Payout destination not found"
  }
}

Cause: The destination_id doesn't exist or doesn't belong to your organization.


409 – Conflict

{
  "error": {
    "code": "CONFLICT",
    "message": "Withdrawal request is being processed. Please retry shortly."
  }
}

Cause: A withdrawal with the same idempotency key is already being processed.


Minimum Withdrawal Amounts

Each currency has a minimum withdrawal amount:

Currency Minimum
USD $10.00
NGN ₦5,000
GHS GH₵50
KES KSh500
USDT_TRC20 $10.00
USDT_ERC20 $50.00

Attempting to withdraw below the minimum will return a 400 Bad Request error.


Withdrawal Processing Time

Processing times vary by destination type and currency:

Destination Type Typical Time
Bank Transfer (NGN) 5-30 minutes
Bank Transfer (USD) 1-3 business days
Mobile Money 1-5 minutes
Crypto (TRC20) 5-15 minutes
Crypto (ERC20) 10-30 minutes

Note: Times are estimates and may vary based on network conditions and blockchain congestion.


Idempotency

What is Idempotency?

Idempotency ensures that retrying the same withdrawal request multiple times only creates one withdrawal.

How to Use

const idempotencyKey = `withdrawal-${orderId}-${timestamp}`;

const withdrawal = await createWithdrawal({
  destination_id: destinationId,
  amount: '100.00',
  currency: 'USD',
  reference: `WD-${orderId}`,
  idempotency_key: idempotencyKey  // Same key = same withdrawal
});

Behavior

  • First request: Creates withdrawal, returns 200 OK
  • Duplicate request (within 30 min): Returns cached response, no new withdrawal created
  • After 30 minutes: Idempotency key expires, new withdrawal can be created

Best practice: Always include an idempotency key for production withdrawals.


Important Notes

Balance Deduction

When a withdrawal is created:

  1. Amount is immediately deducted from your available balance
  2. Funds are moved to "in-flight" state
  3. If withdrawal fails, funds are returned to your balance
  4. If withdrawal succeeds, funds are gone permanently

Withdrawal Fees

Some providers charge withdrawal fees:

  • Bank transfers: Usually free or minimal fee
  • Mobile money: May include processing fees
  • Crypto: Blockchain network fees apply (deducted from withdrawal amount)

Fees are shown in the withdrawal quote (if you create one first) and deducted automatically.

Currency Matching

The withdrawal currency must match the destination currency:

// CORRECT
{
  destination_id: "dest_ngn_bank_account",  // NGN destination
  currency: "NGN",                          // NGN withdrawal
  amount: "100000.00"
}

// WRONG
{
  destination_id: "dest_ngn_bank_account",  // NGN destination
  currency: "USD",                          // USD withdrawal - MISMATCH!
  amount: "100.00"
}

Reference Format

The reference field:

  • Must be unique across your withdrawals
  • Used for reconciliation
  • Visible in your transaction history
  • Helps you track withdrawals in your system

Recommendation:

const reference = `WD-${internalOrderId}-${timestamp}`;

Withdrawal Lifecycle

1. Created

  • Status: PENDING
  • Funds deducted from available balance
  • Queued for processing

2. Processing

  • Status: PROCESSING
  • Sent for processing
  • Validating destination details

3. Completed

  • Status: COMPLETED
  • Funds sent to destination successfully
  • Customer receives funds

4. Failed

  • Status: FAILED
  • Funds returned to your available balance
  • Check status history for failure reason

Monitoring Withdrawals

Option 1: Poll Status

async function waitForWithdrawalCompletion(withdrawalId, maxAttempts = 60) {
  const finalStatuses = ['COMPLETED', 'FAILED', 'CANCELLED'];
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await fetch(
      `https://api.usesyncpay.com/api/v1/payouts/withdrawals/${withdrawalId}`,
      { headers: { 'Authorization': 'Bearer sk_live_...' } }
    );
    
    const withdrawal = await response.json();
    
    if (finalStatuses.includes(withdrawal.status)) {
      return withdrawal;
    }
    
    // Wait 10 seconds
    await new Promise(resolve => setTimeout(resolve, 10000));
  }
  
  throw new Error('Withdrawal status check timed out');
}

Set up webhook notifications to receive real-time withdrawal updates. See Webhook Documentation.


Testing

Test Mode Behavior

In test mode (sk_test_...):

  • Withdrawals are created but no real money moves
  • All statuses are simulated
  • Processing times are instant or near-instant
  • Transaction references are simulated

Simulating Success/Failure

Contact support to manually:

  • Mark test withdrawal as completed
  • Mark test withdrawal as failed
  • Test webhook delivery


Next Steps

After creating a withdrawal:

  1. Monitor status using polling or webhooks
  2. Handle failures by retrying or alerting user
  3. Reconcile completed withdrawals with your accounting system
  4. Update user balance in your application