Transfers and payouts
The current official Node.js and PHP SDKs do not expose a payout client. Use the REST examples on this page; the SDK hub tracks the supported SDK surfaces.
Use payouts to send money to supported bank accounts. A reliable payout flow validates the recipient before sending, uses one stable reference per business operation, and treats the initial API response as acceptance—not necessarily final settlement.
- 01Get bank dataFetch supported banks and refresh your cache deliberately.
- 02Validate recipientShow the returned account name before the user confirms.
- 03Create payoutSubmit one server-side request with one durable ref.
- 04Reconcile outcomeVerify the original ref and deduplicate signed events.
Use a dashboard-issued sandbox API key on your server. Do not enter credentials into this documentation site.
The payout lifecycle
| Step | Your application does | Why it matters |
|---|---|---|
| 1. Get banks | Fetch and cache supported bank codes. | Avoid hardcoded or stale recipient data. |
| 2. Validate recipient | Look up the account name with bank code and account number. | Lets a user confirm who will receive funds. |
| 3. Create payout | Submit a unique ref, amount, bank code, and account number. | Prevents duplicate financial operations. |
| 4. Reconcile | Use the verification endpoint and relevant webhook events. | Confirms final status before marking an order paid. |
1. Fetch and cache supported banks
Use the bank list when your user chooses a bank. Cache it in your application and refresh it on a schedule appropriate to your product.
- cURL
- Node.js
- Python
- Go
curl https://sandbox.payscribe.ng/api/v1/payouts/bank/list \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/payouts/bank/list', {
method: 'GET',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`},
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
response = requests.get(
'https://sandbox.payscribe.ng/api/v1/payouts/bank/list',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
var body io.Reader
request, err := http.NewRequest(http.MethodGet, "https://sandbox.payscribe.ng/api/v1/payouts/bank/list", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
2. Validate the recipient
Perform account-name lookup immediately before the user confirms a payout. Present the returned name for confirmation; do not silently substitute it.
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/payouts/account/lookup \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"bank":"058","account":"0123456789"}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/payouts/account/lookup', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"bank": "058",
"account": "0123456789"
}),
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
import json
payload = json.loads(r'''{
"bank": "058",
"account": "0123456789"
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/payouts/account/lookup',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}", 'Content-Type': 'application/json'},
json=payload,
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"strings"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := strings.NewReader(`{
"bank": "058",
"account": "0123456789"
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/payouts/account/lookup", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
3. Create a single payout
Generate ref in your server before creating the request. Keep that reference when recovering from a timeout or an uncertain result.
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/payouts/transfer \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ref":"payout_order_10021",
"bank_code":"058",
"account_number":"0123456789",
"amount":10000,
"narration":"Invoice settlement"
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/payouts/transfer', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"ref": "payout_order_10021",
"bank_code": "058",
"account_number": "0123456789",
"amount": 10000,
"narration": "Invoice settlement"
}),
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
import json
payload = json.loads(r'''{
"ref": "payout_order_10021",
"bank_code": "058",
"account_number": "0123456789",
"amount": 10000,
"narration": "Invoice settlement"
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/payouts/transfer',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}", 'Content-Type': 'application/json'},
json=payload,
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"strings"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := strings.NewReader(`{
"ref": "payout_order_10021",
"bank_code": "058",
"account_number": "0123456789",
"amount": 10000,
"narration": "Invoice settlement"
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/payouts/transfer", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
The API reference defines request-field units, limits, and the current response schema. Before presenting a final amount to a user, retrieve and display the applicable fee when your workflow requires it.
4. Confirm the final state
Do not consider a payout complete solely because its creation request returned successfully. Verify it by reference and handle the relevant signed webhook event.
- cURL
- Node.js
- Python
- Go
curl "https://sandbox.payscribe.ng/api/v1/payouts/verify/payout_order_10021" \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/payouts/verify/payout_order_10021', {
method: 'GET',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`},
});
const result = await response.json().catch(() => null);
if (!response.ok) throw new Error(`Payscribe request failed: ${response.status}`);
console.log(result);
import os
import requests
response = requests.get(
'https://sandbox.payscribe.ng/api/v1/payouts/verify/payout_order_10021',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
timeout=20,
)
response.raise_for_status()
print(response.json())
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
var body io.Reader
request, err := http.NewRequest(http.MethodGet, "https://sandbox.payscribe.ng/api/v1/payouts/verify/payout_order_10021", body)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer "+os.Getenv("PAYSCRIBE_API_KEY"))
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
responseBody, _ := io.ReadAll(response.Body)
if response.StatusCode < 200 || response.StatusCode > 299 { panic(fmt.Sprintf("Payscribe request failed: %s", response.Status)) }
fmt.Println(string(responseBody))
}
Bulk payouts
Use bulk transfers only after validating the recipient data for every row. Keep a batch reference and record each row's outcome separately; a batch is not a substitute for reconciliation.
See the Transfers API reference for the batch payload and response.
Failure and timeout handling
| Situation | Safe action |
|---|---|
| Invalid recipient input | Ask the user to correct the bank/account details; do not retry unchanged data. |
| Insufficient wallet balance | Stop the payout and resolve funding before retrying. |
| Duplicate reference | Reconcile the existing operation; do not create a new payout blindly. |
| Timeout after submission | Keep the same ref, verify the payout, then decide the next action. |
| Webhook retry | Verify its signature and process its event ID once. |
Use References and duplicate protection, Webhooks, and Common errors when implementing recovery paths.
Was this page helpful?