Collections
Accept payments via virtual accounts, wallet payments, and payment simulation.
Virtual Accounts
Create Permanent Virtual Account
Generate a dedicated NUBAN account number for a customer. Inbound transfers are auto-credited to the customer's wallet.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | The unique identifier of the customer to assign the virtual account to |
bank | string | Yes | The bank provider for the virtual account. Supported values: 9psb, palmpay |
account_type | string | Yes | Must be static for a permanent, reusable account number |
bvn | string | Conditional | Required when bank is palmpay, unless the customer already has a BVN on file from a tier 2 upgrade |
business_name | string | No | Overrides the account name shown to payers (defaults to your registered business/trade name) |
Request
POST /collections/virtual-accounts/create
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_abc12345",
"bank": "9psb",
"account_type": "static"
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"customer_id": "cus_abc12345",
"bank": "9psb",
"account_type": "static"
}),
});
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'''{
"customer_id": "cus_abc12345",
"bank": "9psb",
"account_type": "static"
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create',
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(`{
"customer_id": "cus_abc12345",
"bank": "9psb",
"account_type": "static"
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create", 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))
}
Response
200 OK
{
"status": true,
"description": "Virtual account created successfully.",
"message": {
"details": {
"customer": {
"id": "cus_abc12345",
"name": "John Doe"
},
"account": [
{
"id": "acc_xyz789",
"account_number": "9876543210",
"account_name": "JOHN DOE",
"bank_name": "9PSB",
"bank_code": "120001",
"currency": "NGN",
"account_type": "static"
}
],
"status": "active",
"created_at": "2026-07-20T10:30:00.000Z",
"updated_at": "2026-07-20T10:30:00.000Z"
}
},
"status_code": 200
}
account is always an array — pass multiple values in bank (e.g. ["9psb", "palmpay"]) to open more than one virtual account for the same customer in one call.
Create Dynamic Virtual Account
Generate a temporary account number valid for a single transaction of a fixed amount.
Tracing this request through the live code path (Endpoints::createVirtualAccount() → SandboxApiEndpoints::createVirtualAccount() / App\Libraries\ApiEndpoints::createVirtualAccount()) shows the request body below cannot succeed. The account_type: dynamic branch requires a nested ref, order (amount, description, amount_type, expiry), and customer (name, email, phone) structure — sending the flat body shown here fails validation with a 400 before any account is created. Even a correctly-nested request would still fail: the routed createVirtualAccount() method only ever reads flat customer_id/bank fields and always creates a static account, so there is no code path that actually produces a dynamic, single-use virtual account today. This needs an engineering fix, not a documentation fix — flagging rather than documenting a contract that doesn't exist. Use Create Permanent Virtual Account until this is resolved.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | The unique identifier of the customer |
bank | string | Yes | The bank provider. Supported values: 9psb, palmpay, cashconnect |
account_type | string | Yes | Set to dynamic to create a single-use virtual account |
amount | number | Yes | The exact amount expected for this transaction in kobo (e.g. 500000 for ₦5,000) |
Request
POST /collections/virtual-accounts/create
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_abc12345",
"bank": "palmpay",
"account_type": "dynamic",
"amount": 500000
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"customer_id": "cus_abc12345",
"bank": "palmpay",
"account_type": "dynamic",
"amount": 500000
}),
});
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'''{
"customer_id": "cus_abc12345",
"bank": "palmpay",
"account_type": "dynamic",
"amount": 500000
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create',
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(`{
"customer_id": "cus_abc12345",
"bank": "palmpay",
"account_type": "dynamic",
"amount": 500000
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/create", 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))
}
Response
201 Created
{
"status": true,
"description": "Dynamic virtual account created successfully",
"message": {
"details": {
"account_number": "1234567890",
"account_name": "John Doe",
"bank_name": "PalmPay",
"bank_code": "100033",
"customer_id": "cus_abc12345",
"account_type": "dynamic",
"amount": 500000,
"amount_paid": 0,
"provider": "palmpay",
"is_active": true,
"expires_at": "2026-07-20T23:59:59.000Z",
"created_at": "2026-07-20T10:35:00.000Z"
}
},
"status_code": 200
}
Get Virtual Account Details
Retrieve information about a virtual account using its account number.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Request
GET /collections/virtual-accounts/{account_number}
- cURL
- Node.js
- Python
- Go
curl -X GET https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/9876543210 \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"
const response = await fetch('https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/9876543210', {
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/collections/virtual-accounts/9876543210',
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/collections/virtual-accounts/9876543210", 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))
}
Response
200 OK
{
"status": true,
"description": "Virtual account number fetched successfully",
"message": {
"details": {
"customer": {
"id": "cus_abc12345",
"name": "John Doe"
},
"account": {
"id": "acc_xyz789",
"account_number": "9876543210",
"account_name": "JOHN DOE",
"bank_name": "9PSB",
"bank_code": "120001",
"currency": "NGN",
"account_type": "static"
},
"status": "active",
"created_at": "2026-07-20 10:30:00",
"updated_at": "2026-07-20 10:30:00"
}
},
"status_code": 200
}
This endpoint does not return a live balance for the account — use GET /my-account/balances for your wallet balance.
Deactivate Virtual Account
Deactivate a virtual account to stop accepting inbound transfers.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Only static accounts can be deactivated (there is currently no way to create a dynamic one — see the caution above).
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
account | string | Yes | The account number of the virtual account to deactivate |
Request
POST /collections/virtual-accounts/deactivate
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/deactivate \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account": "9876543210"
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/deactivate', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"account": "9876543210"
}),
});
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'''{
"account": "9876543210"
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/deactivate',
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(`{
"account": "9876543210"
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/deactivate", 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))
}
Response
200 OK
{
"status": true,
"description": "Virtual account updated successfully.",
"message": {
"details": {
"id": "acc_xyz789",
"account": "9876543210",
"status": "inactive",
"updated_at": "2026-07-20 12:00:00"
}
},
"status_code": 200
}
Reactivate Virtual Account
Reactivate a previously deactivated virtual account.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
account | string | Yes | The account number of the virtual account to reactivate |
Request
POST /collections/virtual-accounts/activate
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/activate \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account": "9876543210"
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/activate', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"account": "9876543210"
}),
});
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'''{
"account": "9876543210"
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/activate',
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(`{
"account": "9876543210"
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/activate", 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))
}
Response
200 OK
{
"status": true,
"description": "Virtual account updated successfully.",
"message": {
"details": {
"id": "acc_xyz789",
"account": "9876543210",
"status": "active",
"updated_at": "2026-07-20 12:05:00"
}
},
"status_code": 200
}
Wallet Payments
This is a two-step production-only checkout flow. It is not available in the sandbox API yet, so Scalar restricts both operations to the live server. Do not test it with real customer credentials outside your approved production checkout journey.
The current API accepts the payer's four-digit Payscribe PIN in the first request and sends a one-time password (OTP) to that payer. Collect the PIN only in a secure server-side checkout flow; never log, store, or send it to browser analytics.
1. Start a wallet payment
Verify the payer, create a pending payment, and send the payment OTP. Keep the returned id; it is required to complete the payment.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Payer's Payscribe email address or tag |
pin | string | Yes | Payer's four-digit Payscribe PIN. Do not persist or log it. |
title | string | Yes | Payment title, 5–255 characters |
amount | number | Yes | Amount in the selected currency unit; minimum 1 |
ref | string | Yes | Your unique payment reference; reuse it only when retrying the same payment |
currency | string | No | Currency code; defaults to NGN |
description | string | No | Additional payment description |
Request
POST /collections/wallet/create-order
- cURL
- Node.js
- Python
- Go
curl -X POST https://api.payscribe.ng/api/v1/collections/wallet/create-order \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"username": "payer@example.com",
"pin": "1234",
"title": "Acme order #1042",
"amount": 5000,
"currency": "NGN",
"description": "Monthly subscription",
"ref": "ORDER-1042"
}'
const response = await fetch('https://api.payscribe.ng/api/v1/collections/wallet/create-order', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
username: 'payer@example.com', pin: '1234', title: 'Acme order #1042',
amount: 5000, currency: 'NGN', description: 'Monthly subscription', ref: 'ORDER-1042',
}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.description || `Payscribe request failed: ${response.status}`);
console.log(result.message.details.id);
import os
import requests
response = requests.post(
'https://api.payscribe.ng/api/v1/collections/wallet/create-order',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
json={'username': 'payer@example.com', 'pin': '1234', 'title': 'Acme order #1042',
'amount': 5000, 'currency': 'NGN', 'description': 'Monthly subscription', 'ref': 'ORDER-1042'},
timeout=20,
)
response.raise_for_status()
print(response.json()['message']['details']['id'])
package main
import (
"strings"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := strings.NewReader(`{
"username": "payer@example.com",
"pin": "1234",
"title": "Acme order #1042",
"amount": 5000,
"currency": "NGN",
"description": "Monthly subscription",
"ref": "ORDER-1042"
}`)
request, err := http.NewRequest(http.MethodPost, "https://api.payscribe.ng/api/v1/collections/wallet/create-order", 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))
}
Response
{
"status": true,
"description": "Authentication successful. Please verify the OTP sent to your mail and in-app push notification",
"message": {"details": {
"id": "ee0d9ca4-3d02-4bf7-9a2e-90e5f69bd2d6",
"amount": 5000,
"created_at": "2026-08-28 12:00:00",
"expiry_at": "2026-08-28 14:00:00",
"user": {"id": "cus_abc123", "name": "Jane Doe", "username": "janedoe", "can_pay": true}
}},
"status_code": 200
}
2. Complete the wallet payment
Submit the payment id from step 1 and the five-digit OTP delivered to the payer. The transaction remains pending until this succeeds.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Pending payment ID returned by step 1 |
otp | string | Yes | Five-digit OTP sent to the payer |
Request
POST /collections/wallet/pay
- cURL
- Node.js
- Python
- Go
curl -X POST https://api.payscribe.ng/api/v1/collections/wallet/pay \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"id":"ee0d9ca4-3d02-4bf7-9a2e-90e5f69bd2d6","otp":"12345"}'
const response = await fetch('https://api.payscribe.ng/api/v1/collections/wallet/pay', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({id: paymentId, otp: payerOtp}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.description || `Payscribe request failed: ${response.status}`);
console.log(result.message.details);
import os
import requests
response = requests.post(
'https://api.payscribe.ng/api/v1/collections/wallet/pay',
headers={'Authorization': f"Bearer {os.environ['PAYSCRIBE_API_KEY']}"},
json={'id': payment_id, 'otp': payer_otp},
timeout=20,
)
response.raise_for_status()
print(response.json()['message']['details'])
package main
import (
"strings"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body := strings.NewReader(`{
"id": "ee0d9ca4-3d02-4bf7-9a2e-90e5f69bd2d6",
"otp": "12345"
}`)
request, err := http.NewRequest(http.MethodPost, "https://api.payscribe.ng/api/v1/collections/wallet/pay", 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))
}
Response
{
"status": true,
"description": "Payment successful.!",
"message": {"details": {
"id": "ee0d9ca4-3d02-4bf7-9a2e-90e5f69bd2d6",
"ref": "ORDER-1042",
"amount": 5000,
"fee": 50,
"customer": {"id": "cus_abc123", "name": "Jane Doe"},
"currency": "NGN",
"created_at": "2026-08-28 12:02:00"
}},
"status_code": 200
}
Confirm Payment
Look up a specific payment made to a virtual account, using the session_id returned in the accounts.payment.status webhook.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | The payment's session_id, from the accounts.payment.status webhook or the transaction record |
account_number | string | Yes | The virtual account number the payment was made to |
Request
POST /collections/virtual-accounts/confirm-payment
- cURL
- Node.js
- Python
- Go
curl -X POST https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/confirm-payment \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session_id": "sess_5f4c2b1a9d8e",
"account_number": "9876543210"
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/confirm-payment', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({
"session_id": "sess_5f4c2b1a9d8e",
"account_number": "9876543210"
}),
});
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'''{
"session_id": "sess_5f4c2b1a9d8e",
"account_number": "9876543210"
}''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/confirm-payment',
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(`{
"session_id": "sess_5f4c2b1a9d8e",
"account_number": "9876543210"
}`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/confirm-payment", 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))
}
Response
200 OK
{
"status": true,
"description": "Payment Verification Successful!",
"message": {
"details": {
"trans_id": "a3f2c9d1-7e4b-4f2a-9c3e-1b8d2f6a0c42",
"amount": 5000,
"fee": 32.5,
"currency": "ngn",
"transaction": {
"session_id": "sess_5f4c2b1a9d8e",
"date": "2026-07-20T12:00:00.000Z",
"bank_name": "9PSB",
"sender_account": "0123456789",
"sender_name": "Jane Doe",
"amount": 5000,
"currency": "NGN",
"description": "Test transfer"
},
"account": {
"id": "cus_abc12345",
"name": "Acme Ltd",
"number": "9876543210",
"bank": "9psb",
"account_type": "static"
},
"customer": {
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "08012345678"
},
"settlement": {
"id": "a3f2c9d1-7e4b-4f2a-9c3e-1b8d2f6a0c42",
"amount": 4967.5,
"prev_balance": 1500000,
"balance": 1504967.5,
"status": "success",
"comment": "Sandbox simulate transfer",
"created_at": "Jul, 20 2026 12:00:05"
}
}
},
"status_code": 200
}
The payment must already exist as a transaction against the virtual account (created by a real inbound transfer, or by Simulate Transfer in sandbox) — this endpoint looks it up, it does not create or match a new payment. settlement is only present once the payment has settled to your wallet balance. In sandbox, when the business has a test webhook URL configured, this endpoint dispatches the accounts.payment.status webhook for the transaction.
Simulate Transfer
Simulate an inbound transfer to a virtual account. This endpoint is only available in sandbox mode for testing.
Headers
| Header | Value |
|---|---|
Authorization | Bearer PAYSCRIBE_API_KEY |
Content-Type | application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | A unique reference for the simulated transfer. |
account | string | Yes | The virtual account number to simulate a transfer to. |
amount | number | Yes | The amount to simulate in naira. |
currency | string | Yes | Currency code, e.g. NGN. |
bank | string | Yes | The bank code to simulate the transfer from. |
sender_account_number | string | Yes | The sender's account number. |
sender_name | string | Yes | The sender's account name. |
description | string | Yes | A narration for the transfer. |
name | string | Yes | The name on the virtual account. |
hash | string | Yes | Message hash — see signing below. |
Hash signing. Compute hash as the uppercase SHA512 of your sandbox test secret key (the ps_sk_test_... secret for your sandbox business — not the ps_pk_test_... public key you send in the Authorization header) concatenated with sender_account_number, account, bank, amount, and ref:
hash = uppercase( SHA512( secret_key + sender_account_number + account + bank + amount + ref ) )
Request
POST /collections/virtual-accounts/simulate-transfer
- cURL
- Node.js
- Python
- Go
# Compute the message hash (see signing above)
HASH=$(printf '%s' "$PAYSCRIBE_SECRET_KEY" "0123456789" "9876543210" "9psb" "5000" "SIM-20260720-001" \
| openssl dgst -sha512 | awk '{print $2}' | tr 'a-f' 'A-F')
curl -X POST https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/simulate-transfer \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ref": "SIM-20260720-001",
"account": "9876543210",
"amount": 5000,
"currency": "NGN",
"bank": "9psb",
"sender_account_number": "0123456789",
"sender_name": "Jane Doe",
"description": "Test transfer",
"name": "Acme Ltd",
"hash": "'"$HASH"'"
}'
const response = await fetch('https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/simulate-transfer', {
method: 'POST',
headers: {Authorization: `Bearer ${process.env.PAYSCRIBE_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({ "ref": "SIM-20260720-001", "account": "9876543210", "amount": 5000, "currency": "NGN", "bank": "9psb", "sender_account_number": "0123456789", "sender_name": "Jane Doe", "description": "Test transfer", "name": "Acme Ltd", "hash": "'"$HASH"'" }),
});
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": "SIM-20260720-001", "account": "9876543210", "amount": 5000, "currency": "NGN", "bank": "9psb", "sender_account_number": "0123456789", "sender_name": "Jane Doe", "description": "Test transfer", "name": "Acme Ltd", "hash": "'"$HASH"'" }''')
response = requests.post(
'https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/simulate-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": "SIM-20260720-001", "account": "9876543210", "amount": 5000, "currency": "NGN", "bank": "9psb", "sender_account_number": "0123456789", "sender_name": "Jane Doe", "description": "Test transfer", "name": "Acme Ltd", "hash": "'"$HASH"'" }`)
request, err := http.NewRequest(http.MethodPost, "https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/simulate-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))
}
Response
200 OK
{
"status": true,
"description": "Payment Confirmed!",
"message": {
"details": {
"trans_id": "a3f2c9d1-7e4b-4f2a-9c3e-1b8d2f6a0c42",
"amount": 5000,
"fee": 32.5,
"currency": "NGN",
"transaction": {
"session_id": "sess_5f4c2b1a9d8e",
"date": "2026-07-20T12:00:00.000Z",
"bank_name": "9PSB",
"sender_account": "0123456789",
"sender_name": "Jane Doe",
"amount": 5000,
"currency": "NGN",
"description": "Test transfer"
},
"customer": {
"id": "cus_abc12345",
"name": "Acme Ltd",
"number": "9876543210"
}
}
},
"status_code": 200
}
Webhooks
| Event | Description |
|---|---|
accounts.payment.status | Inbound payment detected and credited to the customer's wallet |
accounts.simulation | Simulate transfer executed on the live test path (the sandbox simulate-transfer endpoint returns without dispatching) |
See Webhooks for payload format and signature verification.
Was this page helpful?