Payments API - Notes and Clarifications

This document highlights important information about the Payments API that may not be immediately obvious from individual endpoint documentation.


Terminology Clarification

Charge vs Payin

These terms are used interchangeably in the SyncPay API:

  • Charge: The technical term for a payment transaction record
  • Payin: Business term for incoming payments (money coming in to your account)

In practice:

  • GET /api/v1/payments/charges/{charge_id} - Get charge details
  • GET /api/v1/payments/payins - List all charges/payins
  • GET /api/v1/payments/payins/{charge_id} - Get payin details (same as getting charge)

Recommendation: Use "payin" when talking about business concepts, "charge" when referring to technical implementation.


Checkout vs Whitelabel Checkout

When to Use Standard Checkout

Use POST /api/v1/payments/checkouts when:

  • You want the simplest integration
  • You're okay redirecting to SyncPay's hosted page
  • You don't need full UI control
  • You want SyncPay to handle payment method selection
  • Quick integration is priority

Flow:

  1. Create checkout → Get URL → Redirect customer → Customer completes → Webhook notification

When to Use Whitelabel Checkout

Use POST /api/v1/payments/whitelabel when:

  • You want to build custom payment UI
  • You're building mobile apps with native experiences
  • You want to embed payments in your app (no redirect)
  • You need full control over the payment flow
  • You're comfortable with more complex integration

Flow:

  1. Create quote → Show quote to customer → Collect payment details → Create whitelabel checkout → Display payment instructions

Important: Whitelabel checkout requires a valid quote (standard checkout doesn't).


Quote Behavior

Quote Expiration

Quotes expire after 30 secs. This is hardcoded and cannot be changed.

Impact:

  • If using whitelabel checkout, you must create the checkout within 30 seconds of generating the quote
  • If quote expires, you'll get a 400 Bad Request with message "Quote expired or not found. Please get a new quote."
  • Solution: Generate a fresh quote

Why 30 seconds?

  • Exchange rates change frequently
  • Fees may change
  • Ensures customers see accurate, up-to-date pricing

Quote Locking

When you create a quote, the following are locked for 10 minutes:

  • Exchange rate
  • Fees
  • Total amount customer pays
  • Gateway/provider selection

This means: Even if exchange rates change globally, your customer pays the quoted amount during the quote's lifetime.


Payment Method Availability

Enabled Payment Methods

Not all payment methods are available to all merchants:

  1. New merchants: Start with basic methods (bank transfer, mobile money)
  2. Verified merchants: Unlock card payments and additional methods
  3. Region-specific: Some methods only work in certain countries

Checking What's Available

To see what payment methods your organization has enabled:

# Get your organization details
curl https://api.usesyncpay.com/api/v1/organizations/me \
  -H "Authorization: Bearer sk_live_..."

# Check "enabled_payment_methods" field

Currency Considerations

Local Pricing Rules

Local pricing can ONLY be used with USD as base currency:

// VALID
{
  "pricing": {
    "base_currency": "USD",
    "amount": "50.00",
    "local_pricing": {
      "NGN": "75000.00"
    }
  }
}

// INVALID
{
  "pricing": {
    "base_currency": "NGN",  // Not USD!
    "amount": "75000.00",
    "local_pricing": {
      "GHS": "620.00"
    }
  }
}

Reason: Local pricing is designed for global USD-based merchants who want to set region-specific prices. Non-USD base currencies are region-specific by nature.


Currency-Specific Precision

Different currencies have different decimal precision:

Currency Type Decimals Example
Most fiat (USD, NGN, GHS) 2 "50.00"
Crypto (USDT, BTC) 2-8 "50.00" or "0.00123456"
Zero-decimal (future) 0 "5000"

Best practice: Always use string format for amounts to preserve precision.


Settlement Currency vs Payment Currency

Important distinction:

  • Payment currency: What the customer pays in
  • Settlement currency: What you receive in your account

Current behavior:

  • Settlement currency = Payment currency
  • If customer pays NGN 75,000, you receive NGN 75,000 (minus fees)

Future enhancement (coming soon):

  • You'll be able to convert settlement to a different currency
  • Example: Customer pays NGN 75,000 → You receive USD $50

Fee Handling

Fee Deduction

Fees are automatically deducted from the payment amount:

Settlement Amount = Payment Amount - Fees

Example:

  • Customer pays: NGN 75,000
  • Total fees: NGN 750
  • You receive: NGN 74,250

Fee Transparency

When creating quotes, fees are shown transparently:

{
  "fees": {
    "total_fee": "750.00",
    "fee_breakdown": {
      "platform_fee": "500.00",
      "gateway_fee": "250.00"
    }
  }
}

However, the amount field already includes fees:

  • amount: What customer sees and pays (inclusive of all fees)
  • settlement_amount: What you receive (after fees deducted)

Checkout Expiration

Default Behavior

  • Standard checkout: Expires in 60 minutes (default)
  • Can be customized: expires_in_minutes (1-1440)
  • Whitelabel checkout: Uses same expiration

What Happens After Expiration

  • Checkout status → EXPIRED
  • Checkout URL becomes invalid (returns error page)
  • If customer paid but checkout expired, payment may still process (edge case)
  • You'll receive a webhook with final status

Recommendation:

  • Short expiration (15-30 min) for time-sensitive offers
  • Longer expiration (24 hours) for invoice-style payments
  • Monitor expiration events via webhooks

Metadata Limitations

Size and Key Limits

{
  "metadata": {
    // Maximum 20 keys
    // Maximum 10KB total size
    // Keys and values must be strings
  }
}

What to Store in Metadata

Good uses:

  • Order IDs
  • Customer IDs
  • Product SKUs
  • Campaign tags
  • Internal reference numbers

Avoid:

  • Sensitive data (passwords, full card numbers)
  • Large objects (user profiles, order details)
  • Binary data

Why?

  • Metadata is returned in webhooks
  • Helps you reconcile payments
  • Used for analytics and reporting

Idempotency

Current State

  • Create Checkout: No built-in idempotency support
  • Create Whitelabel Checkout: No built-in idempotency support
  • Create Quote: No idempotency (quotes are short-lived anyway)

Best Practices

To prevent duplicate checkouts:

  1. Generate unique identifier in your system
  2. Store it in metadata
  3. Check for duplicates before creating checkout
async function createCheckoutIdempotent(orderData) {
  const idempotencyKey = orderData.order_id;
  
  // Check if checkout already exists for this order
  const existing = await db.checkouts.findOne({ 
    metadata: { order_id: idempotencyKey } 
  });
  
  if (existing) {
    return existing;
  }
  
  // Create new checkout
  return await createCheckout({
    ...orderData,
    metadata: {
      order_id: idempotencyKey
    }
  });
}

Webhook Reliability

Best Practice: Combine Polling and Webhooks

Don't rely on webhooks alone for critical payments:

  1. Create checkout/payment
  2. Start polling the charge status endpoint
  3. Listen for webhooks simultaneously
  4. Whichever completes first wins

Why?

  • Webhooks can fail (network issues, server downtime)
  • Polling ensures you eventually get status
  • Combined approach = most reliable

See Webhook Documentation for setup.


Rate Limits

Standard Tier

  • 100 requests per minute per API key
  • Applies to all endpoints
  • Exceeded limit → 429 Too Many Requests

Strategies for High Volume

If you hit rate limits:

  1. Implement exponential backoff
  2. Cache results (especially for list endpoints)
  3. Use webhooks instead of polling
  4. Contact support for increased limits (available for high-volume merchants)

Test Mode vs Live Mode

Data Separation

Test and live modes are completely isolated:

  • Test checkouts don't appear in live mode
  • Test charges don't affect live balances
  • Test webhooks go to test webhook URLs only

Test Mode Limitations

In test mode:

  • No real money moves
  • Some payment providers return simulated responses
  • Exchange rates may be simulated or slightly delayed
  • All features work the same as live mode

Transitioning to Live Mode

To go live:

  1. Complete business verification in dashboard
  2. Wait for approval (1-3 business days)
  3. Generate live API key (sk_live_...)
  4. Replace test key in production environment
  5. Test with small transaction first

Common Pitfalls

1. Using Number Instead of String for Amounts

Wrong:

{
  "pricing": {
    "base_currency": "USD",
    "amount": 50.00  // Number!
  }
}

Correct:

{
  "pricing": {
    "base_currency": "USD",
    "amount": "50.00"  // String!
  }
}

Why: Strings preserve decimal precision and avoid floating-point errors.


2. Not Handling Quote Expiration

Whitelabel checkout requires valid quote. Always:

  1. Check quote expires_at timestamp (quotes expire in 30 seconds)
  2. If expired or close to expiring, generate new quote immediately
  3. Handle 400 Quote expired errors gracefully

3. Polling Forever

Don't poll charge status indefinitely:

  • Set timeout (10-15 minutes recommended)
  • Use exponential backoff
  • Fall back to webhooks if available

4. Hardcoding Payment Methods

Payment methods can change. Always:

  • Call GET /api/v1/payments/payment-methods dynamically
  • Don't hardcode method IDs or currencies in your UI
  • Check for new methods periodically

5. Ignoring Settlement Amount

The amount field is what the customer paid, but settlement_amount is what you actually receive (after fees).

Always use settlement_amount for:

  • Revenue calculations
  • Financial reporting
  • Accounting reconciliation

Deprecated Features

Currently, there are no deprecated endpoints in the Payments API. All documented endpoints are actively supported and maintained.


Migration Notes

If you're migrating from an older integration or another payment provider:

Key Differences from Other Providers

Feature SyncPay Stripe Flutterwave
Checkout flow Hosted + Whitelabel Checkout Sessions Inline + Standard
Multi-currency Built-in Limited Regional
African focus Strong Limited Strong
Crypto support Yes No Limited

Getting Help

When You're Stuck

  1. Check this notes file for clarifications
  2. Review individual endpoint docs for detailed specs
  3. Test in test mode to safely experiment
  4. Contact support with your organization ID (never share your API key!)

Support Channels


Future Enhancements

Features currently in development:

  • Idempotency keys for checkout creation
  • Currency conversion for settlements
  • Timestamp-based filtering for list endpoints
  • Bulk operations API for high-volume merchants
  • Additional payment methods (region-dependent)

Check the changelog or contact support for updates.