References & Duplicate Protection
Payscribe uses the request body ref to prevent duplicate financial operations. A ref is not an HTTP idempotency key that replays the original response.
How it works
Pass a unique ref value on supported write endpoints. Once the reference is already associated with a processed transaction, a repeat request is rejected and no second transaction is created.
This protects payment operations from duplicate client submissions. It does not remove the need to reconcile an operation after a network timeout.
Operations that use ref
| Operation | Duplicate-protection field | Endpoint |
|---|---|---|
| Transfer | ref | POST /api/v1/payouts/transfer |
| Virtual account (dynamic) | ref | POST /api/v1/collections/virtual-accounts/create |
| Card creation | ref | POST /api/v1/cards/create |
| Bills payment | ref | POST /api/v1/bills/* |
| FX quote creation | ref | POST /api/v1/currency-pair, POST /api/v1/execute-quote |
| Simulate transfer | ref | POST /api/v1/collections/virtual-accounts/simulate-transfer |
Best practices
- Always send a
ref— Every supported write operation should include a unique reference. - Use a UUID or order ID — Your internal order or transaction ID works well, for example
ord_abc123. - Reconcile after an uncertain result — If a client times out, keep the same
ref, then look up the original operation before deciding whether to retry. - Use a stable format — Use lowercase letters, digits, underscores, or hyphens; keep it below 100 characters and unique to one business operation.
Example
- 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": "ord_001", "bank_code": "058", "account_number": "0123456789", "amount": 5000}'
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": "ord_001",
"bank_code": "058",
"account_number": "0123456789",
"amount": 5000
}),
});
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": "ord_001",
"bank_code": "058",
"account_number": "0123456789",
"amount": 5000
}''')
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": "ord_001",
"bank_code": "058",
"account_number": "0123456789",
"amount": 5000
}`)
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))
}
Repeat the same request with the same ref. The API rejects the duplicate and does not create a second transfer.
Response on an existing reference:
{
"status": false,
"description": "Duplicate transaction found, please check",
"status_code": 406
}
Recovery after a timeout
If you do not receive a response, do not create a new reference immediately. Keep the original ref, wait briefly, then reconcile through the relevant transaction, transfer, wallet, or dashboard record. Create a new reference only after you have established that the original request did not create an operation.
ref uniqueness is retained for processed transaction records. Do not rely on a fixed expiry window for duplicate protection.
Was this page helpful?