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 id with 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:

TypeDescriptionUse case
providerPayment to an institution or provider.University tuition, school fees.
companyBusiness-to-business payment.Invoices, supplier payments.
privateIndividual/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

FieldTypeRequiredDescription
quoteIdstringYesThe quote ID from the Quotes API.
selectedQuoteVariantIdnumberYesThe index of the chosen variant from the quote.
selectedQuoteVariantBankIdstringNoBank ID if the variant has multiple bank options.
studentInfoobjectYesStudent's firstName, lastName, and email.
payerDetailsobjectYesFull payer information (see Payer details below).
purposestringYesPayment purpose (see Payment purposes below).
countryCodestringYes2-letter ISO 3166-1 country code.
payoutsarrayYesArray of payout objects with purposeProofDocumentId (UUID) and reference (string).
payerIdentityDocumentIdstringYesUUID 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

FieldTypeRequiredDescription
quoteIdstringYesThe quote ID.
selectedQuoteVariantIdnumberYesThe index of the chosen variant.
selectedQuoteVariantBankIdstringNoBank ID if applicable.
payoutsarrayYesArray 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

FieldTypeRequiredDescription
quoteIdstringYesThe quote ID.
selectedQuoteVariantIdnumberYesThe index of the chosen variant.
selectedQuoteVariantBankIdstringNoBank ID if applicable.
payerDetailsobjectYesFull payer information (see Payer details below).
purposestringYesPayment purpose (see Payment purposes below).
countryCodestringYes2-letter ISO 3166-1 country code.
payoutsarrayYesArray of payout objects with purposeProofDocumentId and reference.
purposeProofDocumentIdstringYesUUID of the purpose proof document.
payerIdentityDocumentIdstringYesUUID of the payer identity document.
payerReferencestringNoCustom reference from the payer's side.
payeeReferencestringNoCustom 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.

FieldTypeRequiredDescription
payerTypestringYesOne of: student, parent, spouse, relative, friend, agent, school, other, other-company, education-agency.
emailstringYesPayer's email address.
firstNamestringNoPayer's first name.
lastNamestringNoPayer's last name.
companyNamestringNoCompany name (for business payers).
phonestringNoPhone number with country code.
dobstringNoDate of birth (YYYY-MM-DD).
addressLine1stringYesStreet address.
addressLine2stringNoAdditional address details.
citystringYesCity name.
statestringYesState or province.
postcodestringYesPostal or ZIP code.
countryCodestringYes2-letter country code.
taxIdentificationNumberstringNoTax ID number.

Payment purposes

The purpose field describes why the payment is being made. Available values depend on the use case:

PurposeDescription
undergraduateUndergraduate tuition fees.
postgraduatePostgraduate tuition fees.
high-schoolHigh school fees.
vocationalVocational training fees.
language-courseLanguage course fees.
professional-yearProfessional year program fees.
exchange-programExchange program fees.
accommodationAccommodation payments.
allowance-paymentAllowance or living expenses.
otherOther purpose.
refundRefund of a previous payment.
scholarshipScholarship payment.
loanLoan payment.
other-requestOther specific request.
comissionCommission 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

GatewayValue
Checkout.comcheckout
Voltvolt
Ebanxebanx
Flutterwaveflutterwave
Paramountparamount
Ingenicoingenico
NinePayninepay

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:

StatusDescription
createdPayment has been created but not yet confirmed.
incompletePayment is missing required information.
processingPayment is being processed.
on-holdPayment is on hold (e.g. pending review).
collectedFunds have been collected from the payer.
tradedCurrency exchange has been completed.
paidFunds have been delivered to the payee.
cancelledPayment was cancelled.
refundedPayment was fully refunded.
partially-refundedPayment was partially refunded.
expiredPayment expired before completion.
failedPayment failed during processing.
incomplete-cancelledAn incomplete payment that was cancelled.
ready-to-collectPayment is confirmed and ready for fund collection.
ready-to-tradeFunds collected, ready for currency exchange.
ready-to-payCurrency 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 data field is polymorphic — its shape must match the transactionType.
  • You can only update or cancel payments in created or incomplete status.
  • Amounts are in decimal form (e.g. 5125.5, not cents).
  • The selectedQuoteVariantId corresponds to the index of the variant in the quote response.
  • Document UUIDs (payerIdentityDocumentId, purposeProofDocumentId) must reference previously uploaded documents.
Previous
FX quotes and rates