Guides
Creating a payment
A payment is the core entity in Nexpay — it represents a cross-border money transfer from a payer to a payee. This guide walks you through the full lifecycle: creating a payment, confirming it, initiating checkout, and managing it afterwards.
Before you start
You will need:
- A valid API key to authenticate your requests.
- A payee
id— use the Payees API to create or retrieve one. - A quote
idwith a selected variant — use the Quotes API to create one. - Documents uploaded via the Documents API (for identity and purpose proof).
How payments work
Payments in Nexpay are polymorphic — the shape of the request body depends on the transactionType. There are three types:
| Type | Description | Use case |
|---|---|---|
provider | Payment to an institution or provider. | University tuition, school fees. |
company | Business-to-business payment. | Invoices, supplier payments. |
private | Individual/personal transfer. | Family remittances, personal transfers. |
Each type requires different data fields. The sections below cover each one.
Creating a provider payment
Provider payments are the most common type, used for paying institutions like universities. They require student information, payer details, purpose, and compliance documents.
try {
const response = await fetch('https://api.nexpay.com/v2/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
'Idempotency-Key': '550e8400-e29b-41d4-a716-446655440000',
},
body: JSON.stringify({
"transactionType": "provider",
"data": {
"quoteId": "a1b2c3d4",
"selectedQuoteVariantId": 1,
"countryCode": "AU",
"purpose": "undergraduate",
"studentInfo": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
},
"payerDetails": {
"payerType": "parent",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "+61412345678",
"dob": "1970-03-20",
"addressLine1": "123 Main Street",
"city": "Sydney",
"state": "NSW",
"postcode": "2000",
"countryCode": "AU"
},
"payouts": [
{
"purposeProofDocumentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference": "INV-2026-001"
}
],
"payerIdentityDocumentId": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
}
}),
});
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Idempotency required
Payment creation requires an Idempotency-Key header. See Idempotency for details.
The response includes the created payment with its id and initial status:
{
"id": 123,
"fromAmount": 5125.5,
"fromCurrency": "USD",
"payerRate": 1.0251,
"status": "created",
"transactionType": "provider",
"createdOn": "2026-04-06T10:00:00.000Z",
"quoteId": "a1b2c3d4",
"payerFirstName": "Jane",
"payerLastName": "Doe",
"payerEmail": "jane.doe@example.com",
"payouts": [
{
"id": 1001,
"payeeId": 42,
"payeeName": "University of Sydney",
"toAmount": 5000,
"toCurrency": "AUD",
"settlementChannel": "checkout",
"settlementMethod": "dmt",
"status": "created",
"reference": "INV-2026-001"
}
]
}
Provider payment fields
| Field | Type | Required | Description |
|---|---|---|---|
quoteId | string | Yes | The quote ID from the Quotes API. |
selectedQuoteVariantId | number | Yes | The index of the chosen variant from the quote. |
selectedQuoteVariantBankId | string | No | Bank ID if the variant has multiple bank options. |
studentInfo | object | Yes | Student's firstName, lastName, and email. |
payerDetails | object | Yes | Full payer information (see Payer details below). |
purpose | string | Yes | Payment purpose (see Payment purposes below). |
countryCode | string | Yes | 2-letter ISO 3166-1 country code. |
payouts | array | Yes | Array of payout objects with purposeProofDocumentId (UUID) and reference (string). |
payerIdentityDocumentId | string | Yes | UUID of the uploaded payer identity document. |
Creating a company payment
Company payments are simpler — they don't require student info or compliance documents, just a quote and payout details:
try {
const response = await fetch('https://api.nexpay.com/v2/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
'Idempotency-Key': '660e8400-e29b-41d4-a716-446655440001',
},
body: JSON.stringify({
"transactionType": "company",
"data": {
"quoteId": "a1b2c3d4",
"selectedQuoteVariantId": 1,
"payouts": [
{
"purposeProofDocumentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference": "PO-2026-042"
}
]
}
}),
});
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Company payment fields
| Field | Type | Required | Description |
|---|---|---|---|
quoteId | string | Yes | The quote ID. |
selectedQuoteVariantId | number | Yes | The index of the chosen variant. |
selectedQuoteVariantBankId | string | No | Bank ID if applicable. |
payouts | array | Yes | Array of payout objects with purposeProofDocumentId and reference. |
Creating a private payment
Private payments are for individual transfers. They require payer details, purpose, compliance documents, and optionally custom references:
try {
const response = await fetch('https://api.nexpay.com/v2/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
'Idempotency-Key': '770e8400-e29b-41d4-a716-446655440002',
},
body: JSON.stringify({
"transactionType": "private",
"data": {
"quoteId": "a1b2c3d4",
"selectedQuoteVariantId": 1,
"countryCode": "AU",
"purpose": "allowance-payment",
"payerDetails": {
"payerType": "parent",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "+61412345678",
"addressLine1": "123 Main Street",
"city": "Sydney",
"state": "NSW",
"postcode": "2000",
"countryCode": "AU"
},
"payouts": [
{
"purposeProofDocumentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference": "FAMILY-001"
}
],
"purposeProofDocumentId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"payerIdentityDocumentId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"payerReference": "PAY-REF-001",
"payeeReference": "PAYEE-REF-001"
}
}),
});
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Private payment fields
| Field | Type | Required | Description |
|---|---|---|---|
quoteId | string | Yes | The quote ID. |
selectedQuoteVariantId | number | Yes | The index of the chosen variant. |
selectedQuoteVariantBankId | string | No | Bank ID if applicable. |
payerDetails | object | Yes | Full payer information (see Payer details below). |
purpose | string | Yes | Payment purpose (see Payment purposes below). |
countryCode | string | Yes | 2-letter ISO 3166-1 country code. |
payouts | array | Yes | Array of payout objects with purposeProofDocumentId and reference. |
purposeProofDocumentId | string | Yes | UUID of the purpose proof document. |
payerIdentityDocumentId | string | Yes | UUID of the payer identity document. |
payerReference | string | No | Custom reference from the payer's side. |
payeeReference | string | No | Custom reference from the payee's side. |
Payer details
The payerDetails object is required for provider and private payments. It describes the person or company sending money.
| Field | Type | Required | Description |
|---|---|---|---|
payerType | string | Yes | One of: student, parent, spouse, relative, friend, agent, school, other, other-company, education-agency. |
email | string | Yes | Payer's email address. |
firstName | string | No | Payer's first name. |
lastName | string | No | Payer's last name. |
companyName | string | No | Company name (for business payers). |
phone | string | No | Phone number with country code. |
dob | string | No | Date of birth (YYYY-MM-DD). |
addressLine1 | string | Yes | Street address. |
addressLine2 | string | No | Additional address details. |
city | string | Yes | City name. |
state | string | Yes | State or province. |
postcode | string | Yes | Postal or ZIP code. |
countryCode | string | Yes | 2-letter country code. |
taxIdentificationNumber | string | No | Tax ID number. |
Payment purposes
The purpose field describes why the payment is being made. Available values depend on the use case:
| Purpose | Description |
|---|---|
undergraduate | Undergraduate tuition fees. |
postgraduate | Postgraduate tuition fees. |
high-school | High school fees. |
vocational | Vocational training fees. |
language-course | Language course fees. |
professional-year | Professional year program fees. |
exchange-program | Exchange program fees. |
accommodation | Accommodation payments. |
allowance-payment | Allowance or living expenses. |
other | Other purpose. |
refund | Refund of a previous payment. |
scholarship | Scholarship payment. |
loan | Loan payment. |
other-request | Other specific request. |
comission | Commission payment. |
Confirming a payment
After creating a payment, it starts with created status. You must confirm it to begin processing:
try {
const response = await fetch('https://api.nexpay.com/v2/payments/123/confirm', {
method: 'POST',
headers: {
'Authorization': 'ApiKey your-api-key-here',
},
});
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Initiating checkout
Once confirmed, redirect the payer to a hosted checkout page to collect their funds. Specify the payment gateway and an optional return URL:
try {
const response = await fetch('https://api.nexpay.com/v2/payments/123/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
},
body: JSON.stringify({
"gateway": "checkout",
"returnUrl": "https://yourapp.com/payments/123/status"
}),
});
if (response.ok) {
const data = await response.json();
// Redirect the payer to the checkout page
console.log(data.redirectUrl);
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
The response contains a redirectUrl — redirect the payer's browser to this URL to complete the payment.
Supported gateways
| Gateway | Value |
|---|---|
| Checkout.com | checkout |
| Volt | volt |
| Ebanx | ebanx |
| Flutterwave | flutterwave |
| Paramount | paramount |
| Ingenico | ingenico |
| NinePay | ninepay |
Polling checkout status
After the payer returns from the checkout page, poll the callback endpoint to check the payment status:
try {
const response = await fetch('https://api.nexpay.com/v2/payments/123/checkout/callback', {
method: 'GET',
headers: {
'Authorization': 'ApiKey your-api-key-here',
},
});
if (response.ok) {
const data = await response.json();
console.log(data.status); // e.g. "processing", "collected", "paid"
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Payment status lifecycle
A payment moves through several statuses during its lifecycle:
| Status | Description |
|---|---|
created | Payment has been created but not yet confirmed. |
incomplete | Payment is missing required information. |
processing | Payment is being processed. |
on-hold | Payment is on hold (e.g. pending review). |
collected | Funds have been collected from the payer. |
traded | Currency exchange has been completed. |
paid | Funds have been delivered to the payee. |
cancelled | Payment was cancelled. |
refunded | Payment was fully refunded. |
partially-refunded | Payment was partially refunded. |
expired | Payment expired before completion. |
failed | Payment failed during processing. |
incomplete-cancelled | An incomplete payment that was cancelled. |
ready-to-collect | Payment is confirmed and ready for fund collection. |
ready-to-trade | Funds collected, ready for currency exchange. |
ready-to-pay | Currency traded, ready for payout to the payee. |
Updating a payment
You can update a payment while it is in created or incomplete status. Send a PUT request with the same polymorphic data structure used during creation:
try {
const response = await fetch('https://api.nexpay.com/v2/payments/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
},
body: JSON.stringify({
"transactionType": "provider",
"data": {
"quoteId": "new-quote-id",
"selectedQuoteVariantId": 2,
"studentInfo": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
},
"payerDetails": {
"payerType": "parent",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "+61412345678",
"addressLine1": "123 Main Street",
"city": "Sydney",
"state": "NSW",
"postcode": "2000",
"countryCode": "AU"
},
"purpose": "undergraduate",
"countryCode": "AU",
"payouts": [
{
"purposeProofDocumentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference": "INV-2026-001"
}
],
"payerIdentityDocumentId": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
}
}),
});
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Cancelling a payment
Cancel an incomplete or created payment:
try {
const response = await fetch('https://api.nexpay.com/v2/payments/123/cancel', {
method: 'POST',
headers: {
'Authorization': 'ApiKey your-api-key-here',
},
});
if (response.ok) {
const data = await response.json();
console.log(data.status); // "cancelled"
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Reissuing a payment
If a payment's quote has expired or you need to recreate it with updated details, you can reissue it. This creates a new payment based on the original and optionally cancels it:
try {
const response = await fetch('https://api.nexpay.com/v2/payments/123/reissue', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
'Idempotency-Key': '880e8400-e29b-41d4-a716-446655440003',
},
body: JSON.stringify({
"reason": "Expired quote, need to recreate with new rate",
"cancelOriginalTransaction": true
}),
});
if (response.ok) {
const data = await response.json();
console.log(data); // New payment object
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Idempotency required
Reissuing a payment also requires an Idempotency-Key header.
Resending payment emails
You can resend the payment instruction email for payments in created or incomplete status:
try {
const response = await fetch('https://api.nexpay.com/v2/payments/123/resend', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
},
body: JSON.stringify({
"langCode": "en"
}),
});
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error(error);
}
Payment documents
Nexpay generates PDF documents for completed payments. These are available once the payment reaches the appropriate status.
Contract
Download the payment contract:
const response = await fetch('https://api.nexpay.com/v2/payments/123/contract', {
headers: { 'Authorization': 'ApiKey your-api-key-here' },
});
// Response is a PDF binary stream
Receipt
Download the settlement receipt:
const response = await fetch('https://api.nexpay.com/v2/payments/123/receipt', {
headers: { 'Authorization': 'ApiKey your-api-key-here' },
});
// Response is a PDF binary stream
Payment advice
Payment advice may need to be generated first. The generation endpoint returns HTTP 202 Accepted — poll the download endpoint until the document is ready:
// 1. Trigger generation
await fetch('https://api.nexpay.com/v2/payments/123/advice/generate', {
method: 'POST',
headers: { 'Authorization': 'ApiKey your-api-key-here' },
});
// 2. Download once ready
const response = await fetch('https://api.nexpay.com/v2/payments/123/advice', {
headers: { 'Authorization': 'ApiKey your-api-key-here' },
});
// Response is a PDF binary stream
Things to know
- Payment creation and reissue both require an Idempotency-Key header.
- The
datafield is polymorphic — its shape must match thetransactionType. - You can only update or cancel payments in
createdorincompletestatus. - Amounts are in decimal form (e.g.
5125.5, not cents). - The
selectedQuoteVariantIdcorresponds to the index of the variant in the quote response. - Document UUIDs (
payerIdentityDocumentId,purposeProofDocumentId) must reference previously uploaded documents.