Merchant API

Merchants integrate with Trans Flow only. Trans Flow handles routing to the payment networks behind a single signed API, so adding a new network never changes the contract below.

Quick start
Live in four steps. Base URL https://www.transflowsolution.com
1

Get your API key

Open the Developers tab in your dashboard and create a key. Live keys start with tf_live_, test keys with tf_test_. The key is shown once — store it server-side only.

2

Charge a customer

One POST creates the payment and returns the prompt the customer approves on their phone.

curl -X POST https://www.transflowsolution.com/api/public/transflow/v1/payments \
  -H "X-TransFlow-Key: tf_live_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: order-8125" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_method": "mobile_money",
    "direction": "collection",
    "channel": "MTN",
    "amount": 25.00,
    "reference": "order-8125",
    "walletNumber": "0244000000"
  }'
3

Show the prompt

If the response status is customer_action_required, render the ussdCode behind a Dial now button so the customer can approve instantly.

<a href="tel:*170%23">Dial now</a>
4

Confirm the result

Point your webhook URL at your server and verify before fulfilling, or poll the payment by reference.

curl -H "X-TransFlow-Key: tf_live_xxxxxxxxxxxxxxxx" \
  https://www.transflowsolution.com/api/public/transflow/v1/payments/order-8125
Authentication
Send your Trans Flow key on every request. Add an Idempotency-Key to make retries safe.
X-TransFlow-Key: tf_live_xxxxxxxxxxxxxxxx
Idempotency-Key: order-8125
Content-Type: application/json
Collect from a wallet
POST /api/public/transflow/v1/payments

Every payment declares a payment_method. Today only mobile_money is active; card and bank_transfer are reserved and return a clear “coming soon” error, so your integration will not change when they go live.

{
  "payment_method": "mobile_money",
  "direction": "collection",
  "channel": "MTN",
  "amount": 25.00,
  "reference": "order-8125",
  "walletNumber": "0244000000",
  "customerName": "Ama Mensah",
  "description": "Order 8125"
}

The response carries the normalised status. When the customer must approve a prompt you get customer_action_required plus a ussdCode you can render behind a “Dial now” button.

{
  "data": {
    "id": "0f2c...",
    "reference": "order-8125",
    "status": "customer_action_required",
    "ussdCode": "*170#",
    "promptNickname": "TRANSFLOW",
    "expiresAt": "2026-08-04T04:12:00.000Z"
  }
}
Pay out to a wallet or bank
Same endpoint, direction: payout
{
  "direction": "payout",
  "channel": "BANK",
  "amount": 500.00,
  "reference": "settlement-114",
  "bankCode": "GCB",
  "bankAccountNumber": "1234567890",
  "accountName": "Ama Mensah"
}
Customer convenience fee
The customer pays a small fee on top of your order amount.

Send the amount you are selling for. Trans Flow adds the convenience fee, charges the customer the total, and settles your order amount. Every payment response includes the breakdown, so your checkout can show it before the customer approves the prompt.

{
  "order_amount": 100.00,
  "customer_fee_percentage": 3,
  "customer_fee_amount": 3.00,
  "total_customer_paid": 103.00,
  "currency": "GHS"
}
Verify a payment
GET /api/public/transflow/v1/payments/{id or reference}

Verification asks the network for the authoritative state and reconciles the stored record. The engine also polls in the background with backoff, so a transaction always reaches a final state even if you never call this.

curl -H "X-TransFlow-Key: tf_live_..." \
  https://your-app.lovable.app/api/public/transflow/v1/payments/order-8125
Statuses
Identical across every payment network.
  • pending — accepted, not yet sent to a network
  • customer_action_required — waiting on the prompt or USSD dial
  • processing — the network is working on it
  • successful — funds moved
  • failed — rejected, see failureCode
  • expired — the customer never approved in time
  • cancelled — cancelled before completion
Errors
Stable codes, never provider text.
{ "error": { "code": "idempotency_conflict",
            "message": "This idempotency key was already used with a different payload" } }