Credit
Posts funds into a subscriber wallet. The subscriber gets the value immediately, and your receivable with SmartPay rises by the same amount.
Requires inbound:write and an X-Idempotency-Key.
Request
Your own reference, unique across your account. This is the idempotency key for the money movement: retrying with the same value can never credit twice.
Minor units. 50000 is Le 500.00. Must sit inside the minimum and maximum
agreed for your account.
SLE. Defaults to your account currency; a mismatch is refused.
Beneficiary by SmartPay account number. Supply exactly one identifier.
Beneficiary by phone number, E.164.
Beneficiary by card serial. The card must be assigned.
The token from a prior name enquiry. When supplied it must name the same beneficiary, which stops an account being swapped between the two calls.
Shown on the customer's statement.
Who sent the money — name, account_masked, bank_code. Optional but
strongly encouraged: for an inbound transfer this is the material fact for
AML review.
Any JSON object you want echoed back on the statement.
Response
COMPLETED on success. Otherwise a stable error code — see
Error codes. This field is normative; the
HTTP status is advisory.
The ledger transaction. Quote this in any support conversation.
What actually reached the wallet. Equal to amount while v1 is zero-rated.
What you owe SmartPay after this credit, so you can track your position without a second call.
curl -X POST https://api.smartpay.sl/api/v1/inbound/credit \
-H "Content-Type: application/json" \
-H "X-API-Key-ID: $SMARTPAY_API_KEY_ID" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Idempotency-Key: $IDEM" \
-H "X-Signature: $SIG" \
-d '{
"reference": "UBA-20260819-0001",
"amount": 50000,
"currency": "SLE",
"msisdn": "+23276123456",
"narration": "Transfer from UBA ****1234",
"payer": {
"name": "AMINATA SESAY",
"account_masked": "****1234",
"bank_code": "UBA"
}
}'
{
"success": true,
"code": "COMPLETED",
"message": "Credit posted",
"data": {
"inbound_id": "INB_7KP2M9XR4TQW",
"reference": "UBA-20260819-0001",
"transaction_id": "TXN_4M8XQ2VN7PLK",
"status": "COMPLETED",
"operation": "credit",
"amount": 50000,
"fee": 0,
"net_credited": 50000,
"currency": "SLE",
"beneficiary": {
"account_number": "0012345678",
"account_name": "MOHAMED K."
},
"partner_outstanding": 1300000,
"completed_at": "2026-08-19T14:22:01Z"
}
}
{
"success": false,
"code": "PARTNER_EXPOSURE_LIMIT_EXCEEDED",
"message": "This credit would take the partner past its 2,000,000.00 outstanding limit; settle before sending more",
"data": {
"reference": "UBA-20260819-0001",
"status": "REJECTED",
"failure_code": "PARTNER_EXPOSURE_LIMIT_EXCEEDED"
}
}
{
"success": false,
"code": "REFERENCE_CONFLICT",
"message": "That reference was already used with different request details"
}
{
"success": false,
"code": "MAX_BALANCE_EXCEEDED",
"message": "Transaction would exceed maximum balance limit (250000000)",
"data": {
"reference": "UBA-20260819-0001",
"status": "REJECTED",
"failure_code": "MAX_BALANCE_EXCEEDED"
}
}
Outcomes
status | Terminal | Meaning |
|---|---|---|
COMPLETED | Yes | The wallet was credited |
REJECTED | Yes | Refused before anything moved |
FAILED | Yes | Accepted, then failed. Nothing moved |
REVERSED | Yes | Was credited, later reversed |
PROCESSING | No | In flight. Poll the status endpoint |
v1 always answers with a terminal status. PROCESSING is part of the contract
so that if settlement ever becomes asynchronous, your integration does not
have to change.
Handling a timeout
A new reference is a new transfer, and would credit the customer twice.
GET /api/v1/inbound/transactions/{reference}. A 404 means we never saw
it.
Same reference, fresh X-Nonce and X-Timestamp. You get either the
original outcome or a first-time credit — never two.