NPS JavaScript SDK
Detailed instructions for integrating the NPS JavaScript SDK into your web application
Include the NPS SDK directly from our CDN:
<script src="https://cdn.nelnetpay.com/sdk/v1.0.0/nps.js"></script>Before using the SDK, your backend must obtain a session token from the NPS API.
Step 2.1: Obtain Credentials
- Obtain your secret from NPS
- Store credentials securely
Step 2.2: Create JWT Authentication Token
All NPS API requests require JWT (JSON Web Token) authentication with:
- Algorithm: HS512 (HMAC-SHA512)
- Header:
Authorization: Bearer <JWT_TOKEN> - Required Claims:
sub: Your API Key IDiat: Issued at timestamp (current time)exp: Expiration timestamp
const jwt = require('jsonwebtoken');
function createAuthToken() {
const payload = {
sub: process.env.NPS_API_KEY_ID,
iat: Math.floor(...),
exp: Math.floor(...)
};
return jwt.sign(payload, process.env.NPS_API_KEY, {
algorithm: 'HS512'
});
}Create a backend endpoint that requests a payment session from the NPS API:
app.post('<YOUR BACKEND API>', async (req, res) => {
const authToken = createAuthToken();
const response = await fetch('<NPS API>', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
res.json({
sessionId: data.session.id,
sessiontoken: data.session.token,
expiresAt: data.session.expiresAt
});
});<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.nelnetpay.com/sdk/v1.0.0/nps.js"></script>
</head>
<body>
<!-- Your checkout form collects name, billing address, and amount -->
<form id="checkout-form">
<!-- Payer Information -->
<h2>Payer information</h2>
<input type="email" name="email" placeholder="Email" required />
<input type="text" name="firstName" placeholder="First name" required />
<input type="text" name="lastName" placeholder="Last name" required />
<input type="tel" name="phone" placeholder="Phone" required />
<!-- Payment Method Selection -->
<h2>Payment method</h2>
<input type="number" name="amount" placeholder="Amount" step="0.01" value="49.99" required />
<label>
<input type="radio" name="paymentMethod" value="applepay">
Apple Pay
</label>
<label>
<input type="radio" name="paymentMethod" value="card" checked>
Credit/Debit Card
</label>
<label>
<input type="radio" name="paymentMethod" value="ach">
Bank Account
</label>
<!-- ACH Account Type (shown only when ACH is selected) -->
<div id="ach-account-type" style="display: none;">
<label>
<input type="radio" name="achAccountType" value="checking" checked>
Checking
</label>
<label>
<input type="radio" name="achAccountType" value="savings">
Savings
</label>
</div>
<!-- SDK iframe mounts here and collects card/bank details -->
<div id="payment-form"></div>
<!-- Billing Address -->
<h2>Billing address</h2>
<input type="text" name="streetAddress" placeholder="Street Address" required />
<input type="text" name="apt" placeholder="Apt, suite, etc." />
<input type="text" name="city" placeholder="City" required />
<input type="text" name="state" placeholder="State" required />
<input type="text" name="zipCode" placeholder="ZIP Code" required />
<select name="country" required>
<option value="US">United States</option>
<option value="CA">Canada</option>
</select>
<button type="submit">Pay Now</button>
</form>
<script>
let nps;
async function initializePayment() {
// STEP 4.1: Get session token from your backend
const response = await fetch('<YOUR BACKEND API>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const { sessionToken } = await response.json();
// STEP 4.2: Initialize NPS SDK
nps = new NPS({
sessionId: sessionId
sessionToken: sessionToken,
onReady: handleReady,
onTokenizationComplete: handleTokenization,
onError: handleError
});
const selectedMethod = document.querySelector('input[name="paymentMethod"]:checked').value;
// STEP 4.3: Mount payment form with styling options
nps.mount('#payment-form', {
paymentMethod: selectedMethod,
displayOptions: {
showLabels: true,
showIcons: true,
showErrors: true
},
styling: {
theme: 'light', // or 'dark'
labelStyle: 'stacked', // or 'inline' or 'inline-split'
fieldStyle: 'outlined', // or 'filled' or 'underlined'
fontFamily: 'Inter, sans-serif',
fontSize: '16px',
colors: {
primary: '#3B82F6',
error: '#DC2626',
success: '#10B981',
label: '#374151',
border: '#D1D5DB',
text: '#111827',
background: '#FFFFFF',
focusBorder: '#3B82F6',
helpText: '#6B7280'
},
spacing: {
baseUnit: 8,
fieldHeight: 48,
fieldPadding: 12
},
borderRadius: 6,
borderWidth: 1
}
});
// STEP 4.4: Setup payment method switching
document.querySelectorAll('input[name="paymentMethod"]').forEach(radio => {
radio.addEventListener('change', (e) => {
const method = e.target.value;
// Show/hide ACH account type
document.getElementById('ach-account-type').style.display =
method === 'ach' ? 'block' : 'none';
// Disable Pay Now button for Apple Pay
document.querySelector('button[type="submit"]').disabled =
method === 'applepay';
// Update payment method in iframe
switchPaymentMethod(method);
});
});
// Listen for ACH account type changes
document.querySelectorAll('input[name="achAccountType"]').forEach(radio => {
radio.addEventListener('change', () => switchPaymentMethod('ach'));
});
}
// Switch payment method
function switchPaymentMethod(method) {
let options = {};
if (method === 'ach') {
const accountType = document.querySelector('input[name="achAccountType"]:checked')?.value;
options.accountType = accountType || 'checking';
}
nps.updatePaymentMethod(method, options);
}
// Handle form submission
document.getElementById('checkout-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const firstName = formData.get('firstName');
const lastName = formData.get('lastName');
// Tokenize with amount, name and billing address from your form
try {
const result = await nps.tokenize({
amount: parseFloat(formData.get('amount')),
cardholderName: `${firstName} ${lastName}`,
billingAddress: {
name: `${firstName} ${lastName}`,
street: formData.get('streetAddress'),
city: formData.get('city'),
state: formData.get('state'),
postalCode: formData.get('zipCode'),
country: formData.get('country')
},
metadata: {
}
});
// Token received - send to your backend
console.log('Token:', result.token);
} catch (error) {
console.error('Payment failed:', error);
}
});
function handleReady() {
console.log('Payment form ready');
}
function handleTokenization(result) {
console.log('Token received:', result.token);
// Send token to your backend for processing
}
function handleError(error) {
console.error('Payment error:', error);
}
// Initialize on page load
initializePayment();
</script>
</body>
</html>What Merchants Should Already Have:
- Existing checkout form with amount and billing fields (name, address, etc)
- Form submission handler
- Payment method buttons (card, ACH, Apple Pay)
- ACH account type selector (checking/savings)
What Merchants Need to Add:
- NPS SDK script tag in
<head> - Pass amount and billing fields (name, address, etc)
- Pass ACH account type
- Handle payment method switching
- Container div
<div id="payment-form"></div>where SDK iframe mounts - Styling
- Backend endpoint (created in Step 3)
- Initialize NPS SDK code
- Update form submission to call
nps.tokenize()
Key Features:
- Multiple Payment Methods: Card, ACH, Apple Pay with selection
- Payment Method Switching: SDK updates iframe content when user selects different method
- ACH Account Type: Checking/Savings selection on parent page, passed to iframe
- Apple Pay Handling: Pay Now button disabled when Apple Pay selected (Apple Pay has own button)
- Full Styling Control: Theme, colors, fonts, spacing all customizable
- Amount Collection: Amount field on parent page, passed during tokenization
- Name Handling: Full name combined for cardholderName
- Metadata: Anything else related to payment passed in metadata object in a systematic/similar way
After tokenization succeeds, send the payment token to your backend:
function handleTokenization(result) {
// Send token to your backend
fetch(<YOUR PACKEND API>, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: result.token,
// ... other payment details
})
});
}Configure the SDK with various options to customize behavior and appearance.
nps.mount('#payment-form', {
paymentMethod: 'card',
displayOptions: {
showLabels: true,
showIcons: true,
showErrors: true
},
styling: {
theme: 'light',
labelStyle: 'stacked',
fieldStyle: 'outlined',
fontFamily: 'Inter, sans-serif',
fontSize: '16px',
colors: {
primary: '#3B82F6',
error: '#DC2626',
success: '#10B981'
}
}
});Important: The styling options are applied to the iframe / iframe content (payment fields), not the container element. You would need to add your CSS styling to the #payment-form container but make sure to test it thoroughly .
Refer the styling guide documentation
The following styling options customize the iframe payment form fields:
const styling = {
theme: 'light', // 'light' or 'dark'
labelStyle: 'stacked', // 'stacked' or 'inline' or 'inline-split'
fieldStyle: 'outlined', // 'outlined' or 'filled' or 'underlined'
fontFamily: 'Inter, system-ui, sans-serif',...
fontSize: '16px',
colors: {
label: '#374151',
border: '#D1D5DB',
text: '#111827',
background: '#FFFFFF',
focusBorder: '#3B82F6',
error: '#DC2626',
errorBackground: '#FEF2F2',
success: '#10B981',
helpText: '#6B7280'
},
spacing: {
baseUnit: 8,
fieldHeight: 48,
fieldPadding: 12
},
borderRadius: 6,
borderWidth: 1
};
// Apply styling to iframe content
nps.mount('#payment-form', { styling });Apply CSS as needed:
/* iFrame Container */
.payment-iframe-container {
margin-top: 10px;
min-height: 280px;
height: auto;
background: #e8ebee;
border-radius: 8px;
padding: 20px;
border: 1px solid #e5e7eb;
overflow: visible;
}
/* Compact container for Apple Pay - fits tightly around button */
.payment-iframe-container.applepay-active {
min-height: 80px !important;
height: 80px !important;
padding: 4px !important;
margin-top: 22px !important;
display: flex;
background: transparent !important;
border: none !important;
}Credit/Debit Cardnps.mount('#payment-form', {
paymentMethod: 'card',
cardOptions: {
supportedBrands: ['visa', 'mastercard', 'amex', 'discover'],
requireCvv: true,
showCardIcon: true
}
}); | ACH Bank Transfernps.mount('#payment-form', {
paymentMethod: 'ach'
}); | Apple Pay// Check if Apple Pay is available
if (nps.isApplePayAvailable()) {
nps.mount('#payment-form', {
paymentMethod: 'applepay',
});
} |
Switch between payment methods without remounting:
// Switch to ACH
await nps.updatePaymentMethod('ach');
// Switch back to card
await nps.updatePaymentMethod('card');When tokenizing, you must pass the amount , cardholder name and billing address. The iframe only collects card, bank details, while name, amount and billing address are passed during tokenization:
const result = await nps.tokenize({
amount: 49.99,
cardholderName: 'John Doe',
billingAddress: {
name: 'John Doe',
street: '123 Main St',
city: 'Anytown',
state: 'CA',
postalCode: '90210',
country: 'US'
},
metadata: {
}
});
// Response includes the payment token
console.log(result.token); // Payment token to send to backend
For example-Important:
- The iframe (mounted form) collects: card number, expiration date, CVV
- You collect separately: amount, cardholder name, billing address
- All are combined during
tokenize()call
Clean up when done:
// Remove iframe and clean up resources
nps.destroy();function handleError(error) {
switch (error.code) {
case 'VALIDATION_ERROR':
// Invalid input in payment fields
console.error('Validation failed:', error.fields);
break;
case 'SESSION_EXPIRED':
// Session token has expired
console.error('Session expired, please refresh');
break;
case 'NETWORK_ERROR':
// Network connectivity issue
console.error('Network error, please try again');
break;
case 'TOKENIZATION_FAILED':
// Tokenization request failed
console.error('Failed to tokenize:', error.message);
break;
default:
console.error('Unknown error:', error);
}
}If migrating from an older NPS SDK version: