Virtual accounts
Virtual accounts give a customer a bank account number that routes inbound transfers into your Payscribe collection flow.
- 01Create customerPersist the customer ID before creating an account.
- 02Create accountChoose static for repeat payments or dynamic for one invoice.
- 03Receive paymentThe customer transfers to the issued account.
- 04Verify and reconcileVerify the signed event and match transaction data once.
Use a dashboard-issued sandbox API key on your server. Do not enter credentials into this documentation site.
Choose an account type
| Type | Best for | Behaviour |
|---|---|---|
| Static | Repeat collections for one customer | A persistent account number. |
| Dynamic | A single invoice or time-bound payment | Fixed amount and expiry. |
Technical flow
Create customer → create virtual account → customer transfers → verified webhook → reconcile transaction → update your business state
Do not mark an order paid only from the account-creation response. Verify the signed payment webhook and reconcile its transaction/reference first.
Supported providers
| Provider | Static | Dynamic | Notes |
|---|---|---|---|
| 9PSB | Yes | Yes | Supports both account types. |
| PalmPay | Yes | No | Static accounts require BVN details. |
Create an account
Create a customer first, then create the account using POST /collections/virtual-accounts/create with your server-side API key.
Use the Node.js SDK guide for static or dynamic accounts, or the PHP SDK guide for customer creation and virtual accounts. Both guides keep the complete SDK workflow inside these docs.
- 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_abc123","bank":"9psb"}'
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_abc123",
"bank": "9psb"
}),
});
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_abc123",
"bank": "9psb"
}''')
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_abc123",
"bank": "9psb"
}`)
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))
}
For a dynamic account you would include account_type: "dynamic" with an order amount and expiry, but the public create endpoint currently only issues static 9PSB/PalmPay accounts — see the Collections API reference for the current status of dynamic accounts. Until then, use static accounts for all API-driven collections.
Confirm payment safely
When the transfer arrives, verify accounts.payment.status using the raw-body webhook signature, store the event ID once, and reconcile the resulting transaction. See Webhooks and Sandbox testing.
Build the full flow
Use the Receive a payment via virtual account recipe for customer creation, sandbox simulation, webhook handling, and reconciliation. See the Collections API reference for all parameters and account lifecycle operations.
Was this page helpful?