Skip to main content

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

ParameterTypeRequiredDescription
customer_idstringYesThe unique identifier of the customer to assign the virtual account to
bankstringYesThe bank provider for the virtual account. Supported values: 9psb, palmpay
account_typestringYesMust be static for a permanent, reusable account number
bvnstringConditionalRequired when bank is palmpay, unless the customer already has a BVN on file from a tier 2 upgrade
business_namestringNoOverrides the account name shown to payers (defaults to your registered business/trade name)

Request

POST /collections/virtual-accounts/create
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"
}'

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.

Not currently functional

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

ParameterTypeRequiredDescription
customer_idstringYesThe unique identifier of the customer
bankstringYesThe bank provider. Supported values: 9psb, palmpay, cashconnect
account_typestringYesSet to dynamic to create a single-use virtual account
amountnumberYesThe exact amount expected for this transaction in kobo (e.g. 500000 for ₦5,000)

Request

POST /collections/virtual-accounts/create
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
}'

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY

Request

GET /collections/virtual-accounts/{account_number}
curl -X GET https://sandbox.payscribe.ng/api/v1/collections/virtual-accounts/9876543210 \
-H "Authorization: Bearer $PAYSCRIBE_API_KEY"

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Only static accounts can be deactivated (there is currently no way to create a dynamic one — see the caution above).

Body Parameters

ParameterTypeRequiredDescription
accountstringYesThe account number of the virtual account to deactivate

Request

POST /collections/virtual-accounts/deactivate
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"
}'

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

ParameterTypeRequiredDescription
accountstringYesThe account number of the virtual account to reactivate

Request

POST /collections/virtual-accounts/activate
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"
}'

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.

Keep customer credentials safe

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

ParameterTypeRequiredDescription
usernamestringYesPayer's Payscribe email address or tag
pinstringYesPayer's four-digit Payscribe PIN. Do not persist or log it.
titlestringYesPayment title, 5–255 characters
amountnumberYesAmount in the selected currency unit; minimum 1
refstringYesYour unique payment reference; reuse it only when retrying the same payment
currencystringNoCurrency code; defaults to NGN
descriptionstringNoAdditional payment description

Request

POST /collections/wallet/create-order
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"
}'

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

ParameterTypeRequiredDescription
idstringYesPending payment ID returned by step 1
otpstringYesFive-digit OTP sent to the payer

Request

POST /collections/wallet/pay
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"}'

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

ParameterTypeRequiredDescription
session_idstringYesThe payment's session_id, from the accounts.payment.status webhook or the transaction record
account_numberstringYesThe virtual account number the payment was made to

Request

POST /collections/virtual-accounts/confirm-payment
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"
}'

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

HeaderValue
AuthorizationBearer PAYSCRIBE_API_KEY
Content-Typeapplication/json

Body Parameters

ParameterTypeRequiredDescription
refstringYesA unique reference for the simulated transfer.
accountstringYesThe virtual account number to simulate a transfer to.
amountnumberYesThe amount to simulate in naira.
currencystringYesCurrency code, e.g. NGN.
bankstringYesThe bank code to simulate the transfer from.
sender_account_numberstringYesThe sender's account number.
sender_namestringYesThe sender's account name.
descriptionstringYesA narration for the transfer.
namestringYesThe name on the virtual account.
hashstringYesMessage 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
# 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"'"
}'

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

EventDescription
accounts.payment.statusInbound payment detected and credited to the customer's wallet
accounts.simulationSimulate 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?

Report a docs issue →