Resources
Transfers

Transfers API

The Transfers API allows you to transfer funds between two customers on the Sznd platform. Transfers can be made in the same currency or across different currencies, with automatic exchange rate conversion when needed.

Overview

Transfers enable peer-to-peer money transfers between users on the platform. You can transfer funds:

  • Same Currency: Direct transfer between wallets of the same currency
  • Cross-Currency: Transfer with automatic currency conversion

Two Methods for Transfers

Method 1: Transfer via Quotes (Recommended)

Use the Quotes API with quote_type: TRANSFER to create transfers. This method supports both same-currency and cross-currency transfers, includes fee calculations, and provides better control over the transfer process.

Method 2: Direct Transfer

Use the direct transfer endpoint for same-currency transfers between wallets. This is simpler but only works for transfers within the same currency.


Method 1: Transfer via Quotes

Create a transfer quote and accept it to complete the transfer. This method supports cross-currency transfers and provides detailed fee information.

Step 1: Look Up Recipient by User Tag

Before creating a transfer, you may want to verify the recipient's user tag.

Endpoint: GET /api/v1/users/by-tag

Authentication: Required (JWT Bearer token)

Query Parameters:

  • tag (string, required) - User tag to look up (4-20 alphanumeric characters)

Success Response (200 OK):

{
  "user_id": "123e4567-e89b-12d3-a456-426614174000",
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane.doe@example.com",
  "user_tag": "janedoe123",
  "type_id": 1,
  "type_name": "Individual",
  "role_id": 1,
  "role_name": "CUSTOMER",
  "status": "ACTIVE"
}

Step 2: Create Transfer Quote

Endpoint: POST /api/v1/quotes/v2

Authentication: Required (JWT Bearer token)

Request Body:

{
  "source_currency": "USD",
  "target_currency": "NGN",
  "source_amount": "100.00",
  "quote_type": "TRANSFER",
  "user_tag": "janedoe123",
  "fee_config_id": "2fbdb973-39fd-4217-aa9b-e7b8ccc9e457",
  "payment_method": "FIAT",
  "tz": "UTC",
  "narration": "Payment for services"
}

Required Parameters:

  • source_currency (string) - Currency to transfer from
  • target_currency (string) - Currency to transfer to (can be same as source)
  • source_amount (string) - Amount to transfer in source currency
  • quote_type (string) - Must be TRANSFER
  • user_tag (string) - User tag of the recipient
  • fee_config_id (string, UUID) - Fee configuration ID
  • payment_method (string) - FIAT or CRYPTO
  • tz (string) - Timezone (IANA format)

Optional Parameters:

  • narration (string) - Description for the transfer

Success Response (201 Created):

{
  "quote_id": "123e4567-e89b-12d3-a456-426614174000",
  "source": {
    "currency": "USD",
    "amount": "100.00"
  },
  "target": {
    "currency": "NGN",
    "amount": "150000.00"
  },
  "exchange_rate": "1500.00",
  "fees": {
    "processing_fee": "2.00",
    "deposit_fee": "0.00",
    "payout_fee": "0.00",
    "total_fees": "2.00",
    "fee_currency": "USD"
  },
  "total_payable": "102.00",
  "valid_until": "2025-03-09T01:40:53Z",
  "quote_status": "CREATED",
  "type": "TRANSFER"
}

Step 3: Accept Transfer Quote

Endpoint: POST /api/v1/quotes/{id}/accept

Authentication: Required (JWT Bearer token)

Path Parameters:

  • id (UUID) - Quote ID from Step 2

Success Response (200 OK):

{
  "quote_id": "123e4567-e89b-12d3-a456-426614174000",
  "transaction_id": "223e4567-e89b-12d3-a456-426614174001",
  "transaction_reference": "TRX-20240501-XYZ123",
  "total_payable": "102.00",
  "timeline": {
    "created_at": "2024-03-09T01:10:53Z",
    "valid_until": "2024-03-09T01:40:53Z"
  }
}

Example: Complete Transfer Flow

Step 1: Verify recipient exists

curl -X GET "/api/v1/users/by-tag?tag=janedoe123" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json"

Step 2: Create transfer quote

curl -X POST /api/v1/quotes/v2 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "source_currency": "USD",
    "target_currency": "NGN",
    "source_amount": "100.00",
    "quote_type": "TRANSFER",
    "user_tag": "janedoe123",
    "fee_config_id": "2fbdb973-39fd-4217-aa9b-e7b8ccc9e457",
    "payment_method": "FIAT",
    "tz": "UTC",
    "narration": "Payment for services"
  }'

Step 3: Accept the quote

curl -X POST /api/v1/quotes/{quote_id}/accept \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json"

Example: HTTP Requests

Step 1: Look up user by tag

GET /api/v1/users/by-tag?tag=janedoe123 HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

Step 2: Create transfer quote

POST /api/v1/quotes/v2 HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "source_currency": "USD",
  "target_currency": "NGN",
  "source_amount": "100.00",
  "quote_type": "TRANSFER",
  "user_tag": "janedoe123",
  "fee_config_id": "2fbdb973-39fd-4217-aa9b-e7b8ccc9e457",
  "payment_method": "FIAT",
  "tz": "UTC",
  "narration": "Payment for services"
}

Step 3: Accept quote

POST /api/v1/quotes/{quote_id}/accept HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

Method 2: Direct Transfer (Same Currency Only)

For same-currency transfers, you can use the direct transfer endpoint. This is simpler but only works when both wallets use the same currency.

Endpoint

POST /api/v1/transactions/transfer

Authentication

This endpoint requires JWT authentication. Include your access token in the Authorization header:

Authorization: Bearer <your-access-token>

Request Body

{
  "source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
  "amount": "100.00",
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}

Parameters:

  • source_wallet_id (UUID, required) - Your wallet ID (must belong to authenticated user)
  • target_wallet_id (UUID, required) - Recipient's wallet ID
  • amount (string, required) - Amount to transfer (decimal string)
  • idempotency_key (UUID, required) - Unique key to prevent duplicate transfers

Important Notes

  • Same Currency Required: Both wallets must use the same currency
  • Wallet Ownership: Source wallet must belong to the authenticated user
  • Idempotency: Using the same idempotency_key will return the existing transaction instead of creating a duplicate

Success Response (201 Created)

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "user_id": "111e4567-e89b-12d3-a456-426614174000",
  "transaction_type": "TRANSFER",
  "status": "PENDING",
  "source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
  "source_amount": "100.00",
  "source_currency": "USD",
  "target_amount": "100.00",
  "target_currency": "USD",
  "created_at": "2024-05-01T14:00:00Z",
  "updated_at": "2024-05-01T14:00:00Z"
}

Error Responses

Status CodeDescription
400Invalid request, insufficient funds, or currency mismatch
401Unauthorized - Invalid or missing JWT token
403Forbidden - Source wallet does not belong to authenticated user
404Wallet not found
500Internal server error

Example: Direct Transfer

curl -X POST /api/v1/transactions/transfer \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
    "amount": "100.00",
    "idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
  }'

Example: HTTP Request

POST /api/v1/transactions/transfer HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
  "amount": "100.00",
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}

User Tags

User tags are unique identifiers that allow users to receive transfers without exposing their wallet IDs or email addresses. Each user can set a custom user tag.

Set User Tag

Endpoint: PUT /api/v1/users/tag

Authentication: Required (JWT Bearer token)

Request Body:

{
  "user_tag": "myusertag123"
}

Requirements:

  • 4-20 alphanumeric characters
  • Must be unique across the platform
  • Pattern: ^[a-zA-Z0-9]+$

Success Response (200 OK):

{
  "user_id": "123e4567-e89b-12d3-a456-426614174000",
  "user_tag": "myusertag123",
  "message": "User tag updated successfully"
}

Look Up User by Tag

Endpoint: GET /api/v1/users/by-tag?tag={user_tag}

Authentication: Required (JWT Bearer token)

Query Parameters:

  • tag (string, required) - User tag to look up

Success Response (200 OK):

{
  "user_id": "123e4567-e89b-12d3-a456-426614174000",
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane.doe@example.com",
  "user_tag": "janedoe123",
  "type_name": "Individual",
  "status": "ACTIVE"
}

Transfer Requirements

Prerequisites

  1. Source User must have:

    • Active account
    • Wallet in source currency
    • Sufficient available balance
  2. Recipient User must have:

    • Active account
    • Wallet in target currency (will be created automatically if needed)
    • Valid user tag (for quote-based transfers)
  3. Both users must be:

    • KYC verified (if required for the currency/amount)
    • In good standing

Transfer Limits

Transfer limits may apply based on:

  • User verification status
  • Currency-specific limits
  • Daily/monthly transaction limits

Check the Exchange Rates API to see applicable limits for a transfer.

Currency Conversion

When transferring between different currencies:

  • Exchange rate is locked when the quote is created
  • Fees are calculated and included in the quote
  • Conversion happens automatically upon quote acceptance

Transfer Status

Transfers go through the following statuses:

  • PENDING - Transfer is pending processing
  • PROCESSING - Transfer is being processed
  • COMPLETED - Transfer completed successfully
  • FAILED - Transfer failed
  • CANCELLED - Transfer was cancelled

Check Transfer Status

curl -X GET "/api/v1/transactions/v2/{transaction_id}" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json"

Example: HTTP Request

GET /api/v1/transactions/v2/{transaction_id} HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

Error Handling

Common Errors

Status CodeErrorDescription
400Invalid requestMissing required fields, invalid amounts, or validation errors
401UnauthorizedInvalid or missing JWT token
403ForbiddenSource wallet doesn't belong to user, or transfer to self
404Not foundRecipient user tag not found, or wallet not found
422Limit violationTransfer amount exceeds limits

Error Response Format

{
  "error": "Target user not found",
  "message": "No user found with the provided user tag"
}

Best Practices

  1. Verify Recipient First - Always look up the user tag before creating a transfer to confirm the recipient
  2. Use Idempotency Keys - Always include unique idempotency keys to prevent duplicate transfers
  3. Check Balance - Verify sufficient balance before creating transfers
  4. Handle Errors - Implement proper error handling for failed transfers
  5. Monitor Status - Track transfer status and notify users of completion
  6. Use Quotes for Cross-Currency - Always use the quote method for transfers between different currencies
  7. Store Transaction References - Save transaction references for reconciliation and support

Use Cases

  1. P2P Payments - Enable users to send money to each other
  2. Marketplace Payments - Process payments between buyers and sellers
  3. Gig Economy - Pay freelancers and contractors
  4. Remittances - Send money to family and friends
  5. Business Payments - Transfer funds between business accounts

Webhook Integration

Set up webhooks to receive real-time notifications when transfers complete or fail. This allows you to:

  • Update your system automatically when transfers complete
  • Handle failed transfers and notify users
  • Reconcile transfers in your database

See the Webhooks documentation for more information.


Next Steps