Inbound Funding API
The Inbound Funding API is how a bank, mobile money operator or payment service provider pushes funds from its own channel into an SmartPay SmartPay subscriber wallet.
Money moves in only. Payouts back out to a bank are a separate outbound API and are not part of v1.
Base path /api/v1/inbound · Currency SLE ·
Amounts integer minor units (50000 is Le 500.00)
The four calls that matter
Confirm who the money would reach, and show that name to your customer before you debit them.
Post the funds. Idempotent on your own reference.
After a timeout, ask what actually happened. Never assume.
Return the value if the transfer needs to be undone.
Endpoints
POST /api/v1/inbound/name-enquiry
Resolve a beneficiary. No side effects.
POST /api/v1/inbound/credit
Load the wallet.
GET /api/v1/inbound/transactions/{reference}
What happened to a reference.
POST /api/v1/inbound/reversal
Undo a settled credit.
GET /api/v1/inbound/statement
Reconcile a date range.
GET /api/v1/inbound/balance
What you owe, and your remaining headroom.
Settlement
You do not prefund. There is no float to top up and nothing to maintain before you can send.
Instead SmartPay keeps a receivable account for you. Every credit you post raises what you owe; when you settle, it comes back down. What remains is your live position.
The subscriber gets the value immediately, and your receivable rises by the same amount.
You already debited your own customer, so you are holding the real money.
You wire the accumulated value. SmartPay records it and your receivable falls.
GET /api/v1/inbound/balance gives you three figures:
| Field | Meaning |
|---|---|
outstanding | What you owe SmartPay right now — credits posted, less settled |
total_received | Lifetime value you have pushed in, net of reversals. Never reduced by settlement |
total_settled | Lifetime value you have paid SmartPay |
outstanding is what you reconcile against and settle. total_received is
your volume figure. They answer different questions, so both are returned.
Exposure limits
Some accounts carry an exposure_limit: a ceiling on outstanding. Reach it
and further credits are refused with 402 PARTNER_EXPOSURE_LIMIT_EXCEEDED
until you settle.
If no limit is set for you, exposure_limit is 0 and available_exposure is
null — you are uncapped.
A 402 is not a rejected request. Nothing moved, nothing is wrong with the
payload. Settle what you owe, then retry with the same reference.
Watch available_exposure and settle before it reaches zero, rather than
discovering the ceiling through a wall of refusals mid-morning.
Idempotency
This is the most important part of the integration.
Every write is keyed on (your partner account, your reference). That gives you
one guarantee worth designing around:
Retrying a credit with the same reference can never credit the customer twice.
| What you send | What you get |
|---|---|
| Same reference, same body | 200 with the original response and header Idempotent-Replay: true |
| Same reference, different body | 409 REFERENCE_CONFLICT — nothing is applied |
| Same reference, still in flight | 409 REQUEST_IN_PROGRESS with Retry-After |
So on a timeout or a 5xx, retry with the identical reference. Do not mint
a new one — a new reference is a new transfer.
If you would rather check first, call
GET /api/v1/inbound/transactions/{reference}. A 404 REFERENCE_NOT_FOUND
means we never saw it and it is safe to send.
Beneficiaries
Supply exactly one identifier per request.
| Field | Notes |
|---|---|
account_number | SmartPay-native and unambiguous. Preferred. |
msisdn | Phone number, E.164. Formatting is normalised. |
card_serial | The card must be assigned to a subscriber. |
Sending more than one is a 400 AMBIGUOUS_BENEFICIARY. We refuse rather than
guess which one you meant.
Wallet accounts are never created for you. If a beneficiary has no account,
you get BENEFICIARY_NO_ACCOUNT — that customer is not onboarded with
SmartPay, and you should learn that before you debit anyone.
Fees
v1 is zero-rated: fee is always 0 and net_credited always equals
amount. Both fields are in every response so that if charging is ever enabled
for your integration, nothing about your parsing has to change.