Appearance
PayBridge Integration Guide
This guide walks you through integrating PayBridge into your application.
For the current client launch, the supported contract is:
- programmatic onboarding via
POST /merchants/{id}/onboard(you collect all data in your own UI) — this is the supported onboarding path - approval sync via
merchant.approved/merchant.rejectedwebhooks with polling fallback - hosted checkout via
POST /checkout/sessions
POST /merchants/{id}/onboard/hosted also exists, but it hands the application off to the processor's own form and is not the path this integration is built around.
The embeddable widgets and merchant portal remain available platform capabilities, but they are out of scope for this launch path. See the API Reference for the complete field and endpoint reference.
1. Overview
PayBridge is a payment-processing intermediary that handles:
- Merchant onboarding -- programmatically creates payment processor accounts (XplorPay/Clearent) with referral attribution.
- Checkout -- embeddable "Pay Now" widget with PCI-compliant tokenization via VGS (Very Good Security) and automatic processor failover.
- Webhooks -- real-time notifications for payment and onboarding events forwarded to your application.
How it works:
Your App --> PayBridge API --> VGS Proxy --> Payment Processor
| (Clearent)
|
Webhooks back to your appSensitive data never touches your servers or ours. The VGS Collect JS SDK captures PII fields in secure iframes and tokenizes them before they reach the PayBridge backend. This covers both payment data (card numbers, CVVs, bank accounts) and onboarding PII (EIN, SSN, DOB, routing/account numbers). The backend passes those tokens through a VGS reverse proxy to the processor, which detokenizes them on arrival.
For full endpoint specifications, see the API Reference.
2. Prerequisites
Before you start, you will need:
| Item | How to get it |
|---|---|
| API Key | Provisioned by the PayBridge team. This is a X-API-Key value that identifies your consumer app. |
| VGS Vault ID | Provided alongside your API key. Required for checkout and onboarding widgets. |
| Webhook endpoint | A publicly reachable HTTPS URL on your server to receive event notifications. |
| Sandbox base URL | https://sandbox.api.nfs-pay.com |
| Production base URL | https://api.nfs-pay.com |
Your consumer app registration also includes:
- CORS origins -- the domains allowed to make browser requests to the API (e.g.,
https://digitalshedbuilder.com). One scoped exception is handled by the API itself, not your config: it answers CORS forhttps://js.verygoodvault.comon the/boarding/echopath only, so VGS Collect's in-browser tokenization can read the echo response (AB#9236) — you never register the VGS origin yourself. - Webhook URL -- where PayBridge forwards payment and merchant status events.
Contact the PayBridge team to register your app and receive these credentials.
3. Authentication
All consumer-facing API requests (merchants, payments, checkout, webhooks test) require the X-API-Key header:
X-API-Key: your_api_key_hereThe API key is scoped to your consumer app. All merchants and payments you create are isolated to your app.
Admin operations (creating consumer apps, managing merchants cross-app, etc.) use JWT authentication. Log in via POST /admin/auth/login with email + password to get a token, then pass it as Authorization: Bearer <jwt>. See the API Reference for details.
Rate Limits
| Request type | Limit |
|---|---|
| Read operations (GET) | 100 requests / 60 seconds |
Payment creation (POST /payments) | 10 requests / 60 seconds |
Refunds (POST /payments/{id}/refund) | 5 requests / 60 seconds |
| Other write operations (POST, PUT, DELETE) | 20 requests / 60 seconds |
| Webhook endpoints | 200 requests / 60 seconds |
| Admin endpoints | 30 requests / 60 seconds |
Rate limit headers are included in every response:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97When rate-limited, the API returns 429 Too Many Requests with a Retry-After header.
4. Quick Start: First Payment in 5 Steps
This walkthrough uses curl. Replace $API_KEY with your actual key and $BASE_URL with the appropriate environment URL.
Step 1: Create a merchant
bash
curl -X POST "$BASE_URL/merchants" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Requested-With: PayBridge" \
-d '{
"name": "Digital Shed Builder",
"business_name": "Digital Shed Builder LLC",
"business_type": "llc",
"ein": "27-1234567",
"email": "billing@digitalshedbuilder.com",
"phone": "+15551234567",
"address": {
"street": "100 Commerce Blvd",
"city": "Austin",
"state": "TX",
"zip": "78701",
"country": "US"
}
}'Response (201):
json
{
"id": "a1b2c3d4-...",
"name": "Digital Shed Builder",
"business_name": "Digital Shed Builder LLC",
"status": "active",
"processors": [],
"created_at": "2026-03-10T12:00:00Z"
}Save the id -- this is your merchant_id.
Step 2: Onboard the merchant with a processor
Use programmatic onboarding — the full request body and field reference are in Section 5. The hosted call below is the alternative, shown for completeness only.
bash
curl -X POST "$BASE_URL/merchants/$MERCHANT_ID/onboard/hosted" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Requested-With: PayBridge" \
-d '{
"processor_type": "clearent",
"is_primary": true,
"dba_name": "Digital Shed Builder",
"email": "billing@digitalshedbuilder.com",
"mcc_code": "5211"
}'Response (202):
json
{
"processor_id": "...",
"type": "clearent",
"onboarding_status": "submitted",
"processor_merchant_id": "CLR_12345",
"application_url": "https://boarding.clearent.net/app/...",
"submitted_at": "2026-03-10T12:05:00Z"
}Step 3: Wait for approval
In production, Clearent underwriting typically takes 1-3 business days.
The INT sandbox does fire boarding webhooks, but on its own terms. Auto-approval is triggered only when the DBA name contains [APPR]; without that tag an application never advances. With it, the first status event arrives anywhere from a few seconds to about 45 minutes after submit, and the rest — Manual Review, Approved, then Boarded with the merchant's terminal credentials — follow within a few minutes of each other. Auto-advance is also intermittent: some applications are never advanced and never rejected, and a stalled one looks exactly like a slow one.
So treat INT approval as best-effort rather than something to schedule around. The admin override endpoint (PATCH /admin/processors/{processor_id}/status with {"status":"approved"}) flips a sandbox processor to approved and fires the merchant.approved webhook to the consumer app, so partner integration testing can proceed end-to-end without waiting on INT.
Poll the status endpoint or listen for the merchant.approved webhook:
bash
curl "$BASE_URL/merchants/$MERCHANT_ID/onboard/status" \
-H "X-API-Key: $API_KEY"Response (200):
json
{
"merchant_id": "a1b2c3d4-...",
"processors": [
{
"processor_id": "...",
"type": "clearent",
"onboarding_status": "approved",
"processor_merchant_id": "CLR_12345",
"submitted_at": "2026-03-10T12:05:00Z",
"approved_at": "2026-03-10T14:30:00Z"
}
]
}Step 4: Add a payment method (card)
bash
curl -X POST "$BASE_URL/payments/merchants/$MERCHANT_ID/payment-methods/card" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Requested-With: PayBridge" \
-d '{
"customer_id": "cust_001",
"card_number_token": "tok_4242xxxxxxxx4242",
"cvv_token": "tok_cvv_xxx",
"card_holder_token": "tok_name_xxx",
"expires_at": "2028-06",
"zip": "78701"
}'Response (201):
json
{
"id": "pm_xyz789",
"type": "card",
"last_four": "4242",
"brand": "visa",
"expires_at": "2028-06",
"is_active": true,
"processor_type": "clearent",
"created_at": "2026-03-10T12:10:00Z"
}Note: The
card_number_token,cvv_token, andcard_holder_tokenvalues are VGS tokens, not raw card data. In production, these come from VGS Collect JS running in the browser. See Section 6 for widget integration.
Step 5: Process a payment
bash
curl -X POST "$BASE_URL/payments" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Requested-With: PayBridge" \
-d '{
"merchant_id": "a1b2c3d4-...",
"amount": 75000,
"currency": "USD",
"payment_method_id": "pm_xyz789",
"idempotency_key": "order-1042-payment-v1",
"description": "10x12 Shed - Order #1042",
"invoice_number": "INV-2026-1042",
"customer": {
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com"
},
"metadata": {
"order_id": "1042"
}
}'Response (201):
json
{
"id": "txn_abc123",
"merchant_id": "a1b2c3d4-...",
"amount": 75000,
"currency": "USD",
"status": "posted",
"gateway_payment_id": "CLR_TXN_456",
"payment_method_id": "pm_xyz789",
"posted_at": "2026-03-10T12:15:00Z",
"created_at": "2026-03-10T12:15:00Z"
}The amount field is always in minor units (cents). 75000 = $750.00.
5. Merchant Onboarding
PayBridge supports two onboarding flows. Use programmatic onboarding (Option B) — it is the supported path for API integrations. Hosted onboarding hands the application off to the processor's own form and gives up the control and attribution the API exists to provide.
Option A: Hosted Onboarding (not the supported path)
Creates a hosted application URL at the processor. The merchant completes onboarding via the processor's own form.
Endpoint: POST /merchants/{merchant_id}/onboard/hosted
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
processor_type | string | No (default: "clearent") | Processor to onboard with |
is_primary | bool | No (default: true) | Set as primary processor |
dba_name | string | Yes | Doing Business As name |
email | string | Yes | Merchant contact email |
mcc_code | string | No (default: "6513") | 4-digit Merchant Category Code |
Response includes: application_url -- redirect the merchant to this URL to complete their application.
Option B: Programmatic Onboarding (Recommended)
Submits all merchant details directly via the PayBridge API, triggering a 16-step processor onboarding sequence. Use this when you want to collect merchant data in your own UI for a consistent branded experience.
Raw onboarding PII is accepted only in sandbox/development. In sandbox and local development, sensitive fields (EIN, bank routing/account numbers, SSN, DOB) may be sent as raw values over HTTPS to simplify testing — the example below runs as-is. In staging and production, VGS tokens are required — a raw value in those environments is rejected. VGS tokenization is always required for payment card data, which is handled by the hosted checkout page.
Do not send placeholder strings shaped like tokens (tok_…) in sandbox: any value in that shape is treated as a real VGS token and routed to the tokenization proxy, which fails with Bank account creation failed: VGS proxy request failed. Either send genuine raw values or genuine tokens.
See the API Reference for a complete field reference with examples.
Endpoint: POST /merchants/{merchant_id}/onboard
Request body:
json
{
"processor_type": "clearent",
"is_primary": true,
"business": {
"legal_name": "Digital Shed Builder LLC",
"dba_name": "Digital Shed Builder",
"business_type": "llc",
"ein": "27-1234567",
"mcc_code": "5211",
"website": "https://digitalshedbuilder.com",
"annual_volume": 50000000,
"average_ticket": 75000,
"high_ticket": 250000,
"card_present_percentage": 0
},
"principal": {
"first_name": "John",
"last_name": "Builder",
"title": "Owner",
"email": "john@digitalshedbuilder.com",
"phone": "512-555-1234",
"dob": "1985-06-15",
"ssn": "123-45-6789",
"ssn_last4": "6789",
"country_of_citizenship": "US"
},
"banking": {
"routing_number": "021000021",
"account_number": "123456789",
"account_type": "checking",
"bank_name": "Chase"
},
"physical_address": {
"line1": "100 Commerce Blvd",
"line2": "",
"city": "Austin",
"state_code": "TX",
"zip": "78701",
"country_code": "US"
},
"mailing_address": null
}Business fields:
| Field | Type | Required | Description |
|---|---|---|---|
legal_name | string | Yes | Legal business name |
dba_name | string | Yes | Doing Business As name |
business_type | string | Yes | Entity type: sole_proprietorship, partnership, corporation, llc, non_profit, government, or association_estate_trust. Any other value is rejected with HTTP 422. |
ein | string | Yes | Tax ID / EIN (raw XX-XXXXXXX or VGS token) |
mcc_code | string | No (default: "6513") | 4-digit Merchant Category Code |
website | string | No | Business website URL |
annual_volume | int | No | Estimated annual sales volume in cents |
average_ticket | int | No | Average transaction amount in cents |
high_ticket | int | No | Highest expected transaction in cents |
card_present_percentage | int | No | Card-present percentage (0-100) |
Principal fields:
| Field | Type | Required | Description |
|---|---|---|---|
first_name | string | Yes | First name |
last_name | string | Yes | Last name |
title | string | Yes | Job title (Owner, CEO, etc.) |
email | string | Yes | Email address |
phone | string | Yes | Phone number |
dob | string | No | Date of birth (raw YYYY-MM-DD or VGS token) |
ssn | string | Yes for Clearent | Full SSN (raw XXX-XX-XXXX with optional dashes, or VGS token) — forwarded to processors as Contact.LegalID |
ssn_last4 | string | No | Last 4 digits of SSN — display only, not forwarded to processors |
country_of_citizenship | string | No (default: "US") | Country code |
Banking fields:
| Field | Type | Required | Description |
|---|---|---|---|
routing_number | string | Yes | 9-digit ABA routing number (raw or VGS token) |
account_number | string | Yes | Account number (raw or VGS token) |
account_type | string | No (default: "checking") | checking or savings |
bank_name | string | No | Name of the bank |
Address fields (physical_address and optional mailing_address):
| Field | Type | Required | Description |
|---|---|---|---|
line1 | string | Yes | Street address line 1 |
line2 | string | No | Suite, apt, etc. |
city | string | Yes | City |
state_code | string | Yes | 2-character state code |
zip | string | Yes | ZIP / postal code (5+ chars) |
country_code | string | No (default: "US") | 2-character country code |
Resuming a failed onboarding
The programmatic flow runs a multi-step processor sequence in one request. If a step fails mid-flow (e.g. an invalid routing number at the bank-account step), the endpoint returns HTTP 400 and leaves the processor in draft status with its processor merchantNumber preserved. The error body identifies exactly where it stopped:
json
{
"error": {
"code": "BAD_REQUEST",
"message": "[add_bank_account] Bank account creation failed: invalid routing number",
"details": {
"processor_id": "a1b2c3d4-...",
"merchant_number": "7588000002352334",
"failed_step": "add_bank_account"
},
"retryable": true
}
}To resume, fix the offending field and re-submit the same POST /merchants/{id}/onboard with the corrected payload. PayBridge detects the existing draft and resumes from where it left off — steps that already succeeded are not re-run, so no duplicate processor-side resources are created, and the original merchantNumber is reused. A successful resume returns HTTP 202 with "resumed": true:
json
{
"processor_id": "a1b2c3d4-...",
"type": "clearent",
"onboarding_status": "submitted",
"processor_merchant_id": "7588000002352334",
"resumed": true
}Notes:
- Only
retryablefailures (statusdraft) resume. - Business identity is frozen across a resume. The legal name, DBA, business type, EIN, and owner SSNs must match the original submission — they identify the application already created at the processor. Submitting different identity values returns
409 FROZEN_IDENTITY_CHANGED. You can correct other fields (e.g. a bad routing number) on the failing step. - A failure at the very first step (application creation) leaves no
draftto resume; simply re-submit to start a fresh application. - Resuming is Clearent (XplorPay) only.
Option C: Embedded Onboarding Widget
Drop the onboarding wizard directly into your page. It handles the full multi-step flow (Business Info, Owner Info, Banking, Review) with VGS-secured fields for all PII (EIN, SSN, DOB, routing number, account number).
html
<div id="pb-onboard"
data-partner="your_partner_id"
data-api-url="https://api.nfs-pay.com"
data-api-key="your_api_key"
data-processor-type="clearent"
data-vgs-vault-id="your_vgs_vault_id"
data-vgs-environment="live"
data-redirect="https://digitalshedbuilder.com/onboarding-complete"
data-terms-url="https://digitalshedbuilder.com/terms"
data-privacy-url="https://digitalshedbuilder.com/privacy"
data-support-email="support@digitalshedbuilder.com"
data-status-callback="onOnboardingStatus">
</div>
<script src="https://api.nfs-pay.com/widget/dist/paybridge-widget.umd.js"></script>Widget data-* attributes:
| Attribute | Required | Description |
|---|---|---|
data-partner | No | Your referral partner ID for revenue attribution |
data-api-url | Yes | PayBridge API base URL |
data-api-key | Yes | Your consumer app API key |
data-processor-type | No (default: "clearent") | Processor type |
data-vgs-vault-id | Yes | VGS vault ID for secure bank fields |
data-vgs-environment | No (default: "sandbox") | sandbox or live |
data-redirect | No | URL to redirect to after approval |
data-terms-url | No | Terms of Service URL (shown on review step) |
data-privacy-url | No | Privacy Policy URL (shown on review step) |
data-support-email | No | Support email displayed on confirmation screen |
data-status-callback | No | JavaScript function name on window called with { status, merchantId } |
Status callback example:
javascript
function onOnboardingStatus(event) {
console.log("Status:", event.status); // "submitted", "approved", "rejected", "timeout"
console.log("Merchant:", event.merchantId); // PayBridge merchant ID
}Option D: Custom Onboarding Form with VGS Collect
Build your own onboarding UI while keeping all PII secure. Use VGS Collect JS to render secure iframes for sensitive fields. Your servers and PayBridge only ever see tokens -- VGS detokenizes when forwarding to the processor.
Which fields MUST use VGS Collect iframes:
| Field | VGS field name | Validation |
|---|---|---|
| EIN | ein | /^\d{2}-?\d{7}$/ |
| SSN (full) | ssn | /^\d{3}-?\d{2}-?\d{4}$/ |
| Date of birth | dob | /^\d{4}-\d{2}-\d{2}$/ |
| Routing number | routing_number | /^\d{9}$/ |
| Account number | account_number | /^\d{4,17}$/ |
All other fields (business name, address, phone, email, etc.) are normal form inputs on your page.
Step 1: Load VGS Collect JS
html
<script src="https://js.verygoodvault.com/vgs-collect/2.24.0/vgs-collect.js"></script>CSP requirements (add to your Content-Security-Policy header):
script-src https://js.verygoodvault.com;
frame-src https://js.verygoodvault.com;Step 2: Create VGS Collect instances
Each logical group of sensitive fields needs its own VGS Collect instance:
javascript
const vaultId = "your_vgs_vault_id"; // provided by PayBridge team
const env = "sandbox"; // "sandbox" or "live"
// Business PII (EIN)
// The third argument (a state callback) is required: omitting it throws
// "callback is not a function" at init. Pass a no-op if you don't need state.
const businessCollect = VGSCollect.create(vaultId, env, () => {});
businessCollect.field("#ein-container", {
type: "text",
name: "ein",
placeholder: "27-1234567",
validations: ["required", "/^\\d{2}-?\\d{7}$/"],
css: { fontSize: "14px", padding: "8px" },
});
// Principal PII (DOB, SSN)
const principalCollect = VGSCollect.create(vaultId, env, () => {});
principalCollect.field("#dob-container", {
type: "text",
name: "dob",
placeholder: "YYYY-MM-DD",
validations: ["/^\\d{4}-\\d{2}-\\d{2}$/"],
css: { fontSize: "14px", padding: "8px" },
});
principalCollect.field("#ssn-container", {
type: "text",
name: "ssn",
placeholder: "123-45-6789",
validations: ["required", "/^\\d{3}-?\\d{2}-?\\d{4}$/"],
css: { fontSize: "14px", padding: "8px" },
});
// Banking PII (routing number, account number)
const bankingCollect = VGSCollect.create(vaultId, env, () => {});
bankingCollect.field("#routing-container", {
type: "text",
name: "routing_number",
placeholder: "123456789",
validations: ["required", "/^\\d{9}$/"],
css: { fontSize: "14px", padding: "8px" },
});
bankingCollect.field("#account-container", {
type: "text",
name: "account_number",
placeholder: "000123456789",
validations: ["required", "/^\\d{4,17}$/"],
css: { fontSize: "14px", padding: "8px" },
});Step 3: Submit and collect tokens
Before calling the PayBridge API, submit each VGS Collect instance to tokenize:
javascript
function vgsSubmit(collectInstance) {
return new Promise((resolve, reject) => {
collectInstance.submit("/post", {}, (status, data) => {
if (status >= 200 && status < 300) resolve(data);
else reject(new Error("Tokenization failed"));
});
});
}
async function submitOnboarding() {
// Tokenize all sensitive fields (returns VGS tokens)
const [businessTokens, principalTokens, bankingTokens] = await Promise.all([
vgsSubmit(businessCollect),
vgsSubmit(principalCollect),
vgsSubmit(bankingCollect),
]);
// Merge tokens with your plain form data
const payload = {
processor_type: "clearent",
is_primary: true,
business: {
legal_name: document.getElementById("legal_name").value,
dba_name: document.getElementById("dba_name").value,
business_type: document.getElementById("business_type").value,
...businessTokens, // { ein: "tok_sandbox_ein_abc123" }
},
principal: {
first_name: document.getElementById("first_name").value,
last_name: document.getElementById("last_name").value,
title: document.getElementById("title").value,
email: document.getElementById("email").value,
phone: document.getElementById("phone").value,
...principalTokens, // { dob: "tok_...", ssn: "tok_..." }
},
banking: {
account_type: document.getElementById("account_type").value,
bank_name: document.getElementById("bank_name").value,
...bankingTokens, // { routing_number: "tok_...", account_number: "tok_..." }
},
physical_address: { /* ... normal form fields ... */ },
};
// POST to PayBridge API
const res = await fetch(`${apiUrl}/merchants/${merchantId}/onboard`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": apiKey, "X-Requested-With": "PayBridge" },
body: JSON.stringify(payload),
});
}Step 4: HTML containers
VGS Collect renders iframes inside empty <div> elements. Style the containers, not the iframes:
html
<!-- Sensitive fields — VGS Collect iframes -->
<div id="ein-container" style="height:40px; border:1px solid #ccc; border-radius:4px;"></div>
<div id="dob-container" style="height:40px; border:1px solid #ccc; border-radius:4px;"></div>
<div id="ssn-container" style="height:40px; border:1px solid #ccc; border-radius:4px;"></div>
<div id="routing-container" style="height:40px; border:1px solid #ccc; border-radius:4px;"></div>
<div id="account-container" style="height:40px; border:1px solid #ccc; border-radius:4px;"></div>
<!-- Normal fields — regular <input> elements -->
<input type="text" id="legal_name" placeholder="Legal Business Name">
<input type="text" id="dba_name" placeholder="DBA Name">
<!-- ... etc ... -->Important: VGS iframe values cannot be read back by JavaScript on the host page. If you have a review/confirmation step, show masked placeholders (e.g., "EIN: Provided securely", "SSN: ****") instead of the raw values.
Checking Onboarding Status
Endpoint: GET /merchants/{merchant_id}/onboard/status
This endpoint returns the merchant's onboarding status as persisted from the processor's boarding webhook — the webhook is the authoritative source of status transitions, so this read reflects the latest webhook-confirmed status.
Possible onboarding_status values:
| Status | Meaning |
|---|---|
draft | A boarding step failed partway. Recoverable — re-submit POST /merchants/{id}/onboard to resume from where it stopped (see Resuming a failed onboarding). |
submitted | Application submitted, awaiting review |
pending_review | Under review by the processor |
approved | Merchant account is active and can process payments |
rejected | Application was denied |
Document Upload (KYC) Widget
During underwriting the processor may request supporting documents (bank statements, ID, proof of address). Embed the document-upload widget so the merchant's files go straight from their browser to PayBridge — your servers never touch them. The widget lists the documents that are still outstanding, shows the instructions attached to each request, and uploads each one.
Unlike the onboarding widget, this surface authenticates with a short-lived widget token (not your API key), scoped to a single merchant and the upload_documents action. Mint it server-to-server, then pass it to the browser.
1. Mint a widget token (server-to-server, never expose your API key in the browser):
bash
curl -X POST "$BASE_URL/widget/sessions" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Requested-With: PayBridge" \
-d '{
"merchant_id": "a1b2c3d4-5678-9abc-def0-1234567890ab",
"allowed_actions": ["upload_documents"],
"ttl_seconds": 900
}'
# → { "widget_token": "wt1...", "expires_at": "2026-03-10T12:15:00Z" }2. Embed the widget with that token:
html
<div id="pb-kyc"
data-api-url="https://api.nfs-pay.com"
data-widget-token="wt1..."
data-on-uploaded="onDocUploaded"
data-on-error="onDocError">
</div>
<script src="https://api.nfs-pay.com/widget/dist/paybridge-widget.umd.js"></script>Widget data-* attributes:
| Attribute | Required | Description |
|---|---|---|
data-api-url | Yes | PayBridge API base URL |
data-widget-token | Yes | Short-lived widget token granted the upload_documents action (from POST /widget/sessions) |
data-on-uploaded | No | window-level JS callback name, invoked with { document_id } after a successful upload |
data-on-error | No | window-level JS callback name, invoked with { document_id, message } on failure |
The widget reads the outstanding documents from GET /widget/documents and uploads to POST /widget/documents/{document_id}/upload with the widget token — your API key is never exposed in the browser. Document-lifecycle changes (document.uploaded, document.accepted, document.rejected) are also delivered to your registered webhook URL; see the Webhook guide.
6. Payment Processing
Option A: Embedded Checkout Widget (Recommended)
The checkout widget handles VGS tokenization, payment submission, error display, and redirect -- all in a single HTML snippet. It supports both card and bank account payments via tabbed UI.
html
<div id="pb-checkout"
data-merchant="your_merchant_id"
data-amount="75000"
data-currency="USD"
data-api-url="https://api.nfs-pay.com"
data-api-key="your_api_key"
data-vgs-vault-id="your_vgs_vault_id"
data-vgs-environment="live"
data-redirect="https://digitalshedbuilder.com/thank-you"
data-on-success="onPaymentSuccess"
data-on-error="onPaymentError">
</div>
<script src="https://api.nfs-pay.com/widget/dist/paybridge-widget.umd.js"></script>Widget data-* attributes:
| Attribute | Required | Description |
|---|---|---|
data-merchant | Yes | Merchant ID (from Step 1) |
data-amount | Yes | Amount in cents (e.g., 75000 = $750.00) |
data-currency | No (default: "USD") | ISO 4217 currency code |
data-api-url | Yes | PayBridge API base URL |
data-api-key | Yes | Your consumer app API key |
data-vgs-vault-id | Yes | VGS vault ID for secure card/bank fields |
data-vgs-environment | No (default: "sandbox") | sandbox or live |
data-redirect | No | URL to redirect to after successful payment |
data-on-success | No | JavaScript function name on window for success callback |
data-on-error | No | JavaScript function name on window for error callback |
Callback examples:
javascript
function onPaymentSuccess(data) {
// data = { transaction_id: "txn_...", status: "success", amount: 75000 }
console.log("Payment succeeded:", data.transaction_id);
}
function onPaymentError(data) {
// data = { error_code: "PAYMENT_DECLINED", message: "Your payment was declined..." }
console.log("Payment failed:", data.error_code, data.message);
}Redirect behavior: On success, the widget redirects to data-redirect with query parameters:
pay_txn_id-- transaction IDpay_status--"success"pay_amount-- amount in cents
Example: https://digitalshedbuilder.com/thank-you?pay_txn_id=txn_abc123&pay_status=success&pay_amount=75000
Security note: The widget validates redirect URLs against the embedding page's origin. Cross-origin redirects,
javascript:URIs, anddata:URIs are blocked. Thedata-redirectURL must be same-origin as the page embedding the widget or match thedata-api-urlorigin.
Option B: Direct API (Advanced)
If you need full control over the UI, call the API directly. You are responsible for VGS Collect integration on your frontend.
Add a card payment method
POST /payments/merchants/{merchant_id}/payment-methods/card
json
{
"customer_id": "cust_001",
"card_number_token": "tok_4242xxxxxxxx4242",
"cvv_token": "tok_cvv_xxx",
"card_holder_token": "tok_name_xxx",
"expires_at": "2028-06",
"zip": "78701",
"billing_address": {
"street": "456 Oak Ave",
"city": "Austin",
"state": "TX",
"zip": "78702",
"country": "US"
}
}| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | Your customer identifier |
card_number_token | string | Yes | VGS token for card number |
cvv_token | string | Yes | VGS token for CVV |
card_holder_token | string | Yes | VGS token for cardholder name |
expires_at | string | Yes | Expiry in YYYY-MM format |
zip | string | Yes | Billing ZIP code |
billing_address | object | No | Full billing address |
Add a bank payment method
POST /payments/merchants/{merchant_id}/payment-methods/bank
json
{
"customer_id": "cust_001",
"account_holder_token": "tok_holder_xxx",
"account_number_token": "tok_acct_xxx",
"routing_number_token": "tok_rout_xxx",
"account_type": "checking"
}| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | Your customer identifier |
account_holder_token | string | Yes | VGS token for account holder name |
account_number_token | string | Yes | VGS token for account number |
routing_number_token | string | Yes | VGS token for routing number |
account_type | string | No (default: "checking") | checking or savings |
Process a payment
POST /payments
json
{
"merchant_id": "a1b2c3d4-...",
"amount": 75000,
"currency": "USD",
"payment_method_id": "pm_xyz789",
"description": "10x12 Shed - Order #1042",
"invoice_number": "INV-2026-1042",
"customer": {
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com"
},
"billing_address": {
"street": "456 Oak Ave",
"city": "Austin",
"state": "TX",
"zip": "78702",
"country": "US"
},
"metadata": {
"order_id": "1042"
}
}| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | Yes | Merchant ID |
amount | int | Yes | Amount in cents |
currency | string | No (default: "USD") | ISO 4217 currency code |
payment_method_id | string | Yes* | Payment method ID (from add card/bank) |
idempotency_key | string | Yes | Unique key for this request (prevents double-charges on retry). Use your order ID or a UUID. Max 255 chars. |
description | string | No | Payment description |
invoice_number | string | No | Invoice reference |
customer | object | No | Customer info (first_name, last_name, email) |
billing_address | object | No | Billing address |
metadata | object | No | Arbitrary key-value pairs for your records |
customer_email | string | No | Customer email (for portable methods) |
customer_payment_method_id | string | No* | Portable payment method ID (alternative to payment_method_id) |
* Exactly one of payment_method_id or customer_payment_method_id is required.
Stored bank (ACH) methods. Charging a stored bank method here is not yet supported and returns a
400— a bank charge needs the raw VGS routing/account tokens, which a stored method does not hold. The supported ACH path is the hosted checkout, where those tokens are collected at pay time. Card methods are unaffected (AB#9519).
Payment statuses:
| Status | Meaning |
|---|---|
posted | Payment completed successfully |
declined | Declined by processor |
failed | Transient processor error |
refunded | Payment was refunded |
Multi-gateway failover: If the primary processor returns a transient error and a secondary processor is configured, PayBridge automatically retries on the secondary. Terminal errors (insufficient funds, invalid card) are not retried. If failover occurred, the response includes failover_from_id.
Issue a refund
POST /payments/{payment_id}/refund
json
{
"amount": 25000,
"idempotency_key": "refund-order-1042-v1",
"description": "Partial refund - customer credit"
}| Field | Type | Required | Description |
|---|---|---|---|
amount | int | No | Refund amount in cents. Omit for full refund. |
idempotency_key | string | Yes | Unique key for this refund request (prevents duplicate refunds on retry). |
description | string | No | Reason for refund |
Retrieve a payment
GET /payments/{payment_id}
Returns the full transaction record including status, gateway_payment_id, posted_at, and failover_from_id.
Option C: Hosted Checkout Sessions (Recommended for Server-to-Server)
The simplest integration path. Your backend creates a checkout session, then redirects the customer to the hosted checkout URL. PayBridge handles the payment form, VGS tokenization, and payment processing. No frontend integration required on your side.
Step 1: Create a checkout session
bash
curl -X POST "$BASE_URL/checkout/sessions" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-Requested-With: PayBridge" \
-d '{
"merchant_id": "a1b2c3d4-...",
"amount": 75000,
"currency": "USD",
"success_url": "https://yoursite.com/payment-success",
"cancel_url": "https://yoursite.com/payment-cancelled",
"description": "Order #12345",
"customer_email": "customer@example.com"
}'Response (201):
json
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"checkout_url": "https://api.nfs-pay.com/checkout/session/f47ac10b-58cc-4372-a567-0e02b2c3d479?st=v1.1710168600.abc123def456...",
"amount": 75000,
"currency": "USD",
"status": "pending",
"expires_at": "2026-03-11T15:30:00Z",
"created_at": "2026-03-11T15:00:00Z"
}Fields:
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | Yes | Merchant ID |
amount | int | Yes | Amount in cents (min 1, max 99999999) |
currency | string | No | Default "USD" |
success_url | string | Yes | Where to redirect after successful payment |
cancel_url | string | No | Where to redirect on cancellation |
description | string | No | Payment description (max 500 chars) |
customer_email | string | No | Pre-fill customer email on checkout page |
idempotency_key | string | No | Prevents duplicate sessions on retry |
Security:
success_urlandcancel_urlmust match your app's registeredcors_origins. This prevents open-redirect attacks.
Step 2: Redirect the customer
Redirect your customer to the checkout_url from the response. The hosted checkout page handles:
- Card/bank entry via VGS Collect (PCI-compliant)
- Payment submission
- Error display and retry
- Redirect to your
success_urlon success orcancel_urlon cancel
Step 3: Handle the redirect
After payment, the customer is redirected to your success_url with query parameters:
https://yoursite.com/payment-success?pay_session_id=f47ac10b-58cc-4372-a567-0e02b2c3d479&pay_status=successStep 4: Verify payment status
Poll the session status to confirm payment:
bash
curl "$BASE_URL/checkout/sessions/$SESSION_ID" \
-H "X-API-Key: $API_KEY"Response:
json
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "completed",
"amount": 75000,
"currency": "USD",
"merchant_id": "a1b2c3d4-...",
"payment_id": "txn_abc123",
"expires_at": "2026-03-11T15:30:00Z",
"completed_at": "2026-03-11T15:05:00Z"
}Session statuses: pending → completed | expired
You can also manually expire a pending session:
bash
curl -X POST "$BASE_URL/checkout/sessions/$SESSION_ID/expire" \
-H "X-API-Key: $API_KEY" \
-H "X-Requested-With: PayBridge"Note: Sessions expire automatically after 30 minutes.
Transaction History
List transactions for a merchant with optional filtering and pagination:
bash
curl "$BASE_URL/payments/merchants/$MERCHANT_ID/transactions?status=posted&limit=20&offset=0" \
-H "X-API-Key: $API_KEY"Response:
json
{
"items": [
{
"id": "txn_abc123",
"merchant_id": "a1b2c3d4-...",
"amount": 75000,
"currency": "USD",
"status": "posted",
"description": "Order #12345",
"posted_at": "2026-03-11T15:05:00Z",
"created_at": "2026-03-11T15:00:00Z"
}
],
"total": 42,
"limit": 20,
"offset": 0
}Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by status (pending, posted, declined, refunded, etc.) |
limit | int | 50 | Page size (1–100) |
offset | int | 0 | Number of items to skip |
Payment Methods
List active payment methods for a merchant:
bash
curl "$BASE_URL/payments/merchants/$MERCHANT_ID/payment-methods" \
-H "X-API-Key: $API_KEY"Response:
json
[
{
"id": "pm_abc123",
"type": "card",
"last_four": "4242",
"brand": "visa",
"expires_at": "2028-12",
"is_active": true,
"created_at": "2026-03-10T12:00:00Z"
}
]7. Webhooks
PayBridge forwards processor events to your registered webhook URL as standardized JSON payloads.
Event Types
Payment events (forwarded to your webhook URL):
| Event | Trigger |
|---|---|
payment.completed | Payment settled or approved by processor |
payment.declined | Payment declined by processor |
payment.refunded | Refund processed |
Merchant events (forwarded to your webhook URL):
| Event | Trigger |
|---|---|
merchant.approved | Merchant onboarding application approved |
merchant.rejected | Merchant onboarding application rejected |
Checkout events (forwarded to your webhook URL):
| Event | Trigger |
|---|---|
checkout.completed | Hosted checkout session payment succeeded |
Payload Format
Payment event:
json
{
"event_type": "payment.completed",
"webhook_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"payment_id": "txn_abc123",
"merchant_id": "a1b2c3d4-...",
"amount": 75000,
"status": "posted",
"timestamp": "2026-03-10T12:15:30.123456+00:00"
}Merchant event:
json
{
"event_type": "merchant.approved",
"webhook_id": "a23bc45d-67ef-8901-a234-5b6c7d8e9f01",
"merchant_id": "a1b2c3d4-...",
"processor_type": "clearent",
"processor_merchant_id": "CLR_12345",
"onboarding_status": "approved",
"timestamp": "2026-03-11T14:00:00.000000+00:00"
}Checkout event:
json
{
"event_type": "checkout.completed",
"webhook_id": "c45de67f-8901-2bcd-ef34-567890123456",
"session_id": "session-uuid",
"payment_id": "payment-uuid",
"merchant_id": "a1b2c3d4-...",
"amount": 75000,
"currency": "USD",
"status": "posted",
"timestamp": "2026-03-26T12:02:30.000000+00:00"
}Note: The
webhook_idfield is a unique identifier for the webhook delivery. Use it as a deduplication key to prevent processing the same event twice on retries.
Responding to Webhooks
Your endpoint should return a 2xx status code to acknowledge receipt. PayBridge currently delivers webhooks on a best-effort basis (fire-and-forget with a 10-second timeout). For guaranteed delivery, poll the status APIs as a fallback.
Setting Your Webhook URL
Your webhook URL is configured when your consumer app is provisioned. To update it, contact the PayBridge team or use the admin API (requires admin JWT from POST /admin/auth/login):
bash
curl -X PUT "$BASE_URL/admin/apps/$APP_ID/webhook-url" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ADMIN_JWT" \
-d '{ "webhook_url": "https://digitalshedbuilder.com/api/pb-webhooks" }'Signature Verification
Every outbound webhook includes an X-PayBridge-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature to ensure the webhook was sent by PayBridge and was not tampered with.
The signature format is: sha256=<hex-encoded HMAC-SHA256>
The HMAC is computed over the raw JSON request body using your app's webhook_secret (returned when your consumer app is provisioned via POST /admin/apps).
Verification steps:
- Read the raw request body bytes (do not parse JSON first).
- Compute HMAC-SHA256 of the body using your
webhook_secret. - Compare your computed signature with the
X-PayBridge-Signatureheader using a constant-time comparison. - Reject the request if the signatures do not match.
Additional recommendations:
- Use HTTPS for your webhook endpoint to prevent tampering in transit.
- Validate that the
merchant_idin the payload belongs to your application.
Idempotency: Use the webhook_id field in the payload as a deduplication key. PayBridge deduplicates inbound processor webhooks, but as a best practice, implement idempotent handling on your side to gracefully handle retries.
Example: Python webhook handler
python
import hashlib
import hmac
import json
from fastapi import FastAPI, Header, HTTPException, Request
app = FastAPI()
WEBHOOK_SECRET = "your-webhook-secret-from-provisioning"
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
"""Verify X-PayBridge-Signature header."""
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.post("/api/pb-webhooks")
async def handle_pb_webhook(
request: Request,
x_paybridge_signature: str = Header(..., alias="X-PayBridge-Signature"),
):
body = await request.body()
if not verify_signature(body, x_paybridge_signature, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = json.loads(body)
event_type = payload.get("event_type")
webhook_id = payload.get("webhook_id")
# Deduplicate using webhook_id
# if already_processed(webhook_id): return {"status": "ok"}
if event_type == "payment.completed":
payment_id = payload["payment_id"]
amount = payload["amount"]
# Update your order status, send receipt, etc.
print(f"Payment {payment_id} completed: ${amount / 100:.2f}")
elif event_type == "payment.declined":
payment_id = payload["payment_id"]
# Notify customer, update order status
print(f"Payment {payment_id} declined")
elif event_type == "payment.refunded":
payment_id = payload["payment_id"]
amount = payload["amount"]
print(f"Payment {payment_id} refunded: ${amount / 100:.2f}")
elif event_type == "merchant.approved":
merchant_id = payload["merchant_id"]
# Enable payment processing for this merchant
print(f"Merchant {merchant_id} approved")
elif event_type == "merchant.rejected":
merchant_id = payload["merchant_id"]
# Notify merchant, suggest re-application
print(f"Merchant {merchant_id} rejected")
return {"status": "ok"}Example: Node.js webhook handler
javascript
const express = require("express");
const crypto = require("crypto");
const app = express();
// Use raw body for signature verification
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));
const WEBHOOK_SECRET = "your-webhook-secret-from-provisioning";
function verifySignature(rawBody, signature, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post("/api/pb-webhooks", (req, res) => {
const signature = req.headers["x-paybridge-signature"];
if (!signature || !verifySignature(req.rawBody, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
const { event_type, webhook_id, payment_id, merchant_id, amount } = req.body;
// Deduplicate using webhook_id
// if (alreadyProcessed(webhook_id)) return res.json({ status: "ok" });
switch (event_type) {
case "payment.completed":
console.log(`Payment ${payment_id} completed: $${(amount / 100).toFixed(2)}`);
// Update order status, send receipt
break;
case "payment.declined":
console.log(`Payment ${payment_id} declined`);
// Notify customer
break;
case "payment.refunded":
console.log(`Payment ${payment_id} refunded: $${(amount / 100).toFixed(2)}`);
break;
case "merchant.approved":
console.log(`Merchant ${merchant_id} approved`);
// Enable payment processing
break;
case "merchant.rejected":
console.log(`Merchant ${merchant_id} rejected`);
break;
default:
console.log(`Unknown event: ${event_type}`);
}
res.json({ status: "ok" });
});
app.listen(3000);8. Testing
Sandbox Environment
| Resource | URL |
|---|---|
| API | https://sandbox.api.nfs-pay.com |
| API Docs (Swagger) | https://sandbox.api.nfs-pay.com/docs |
| Widget JS | https://sandbox.api.nfs-pay.com/widget/dist/paybridge-widget.umd.js |
| VGS Environment | sandbox |
Use your sandbox API key for all test requests. Sandbox transactions do not hit real processors.
Test Scenarios
Checkout widget:
- Embed the checkout widget with
data-vgs-environment="sandbox"and your sandbox VGS vault ID. - Fill in test card details in the VGS Collect iframes.
- Verify the success callback fires and the redirect includes
pay_txn_id. - Test error handling: use an invalid amount (0 or negative) to trigger a validation error.
Onboarding widget:
- Embed the onboarding widget with sandbox credentials.
- Walk through all four steps (Business Info, Owner Info, Banking, Review).
- Verify the status polling screen appears after submission.
- Confirm the
data-status-callbackfunction is called with{ status: "submitted", merchantId: "..." }.
API direct:
- Create a merchant via
POST /merchants. - Submit onboarding via
POST /merchants/{id}/onboard/hosted. - Poll
GET /merchants/{id}/onboard/statusuntil status changes. - Add a payment method and process a test payment.
- Issue a refund.
Error Codes to Test Against
| Scenario | Expected Error Code | HTTP Status |
|---|---|---|
| Missing API key | UNAUTHORIZED | 401 |
| Invalid API key | UNAUTHORIZED | 401 |
| Merchant not found | RESOURCE_NOT_FOUND | 404 |
| Invalid request body | VALIDATION_ERROR | 422 |
| Payment declined | PAYMENT_DECLINED | 402 |
| Processor error | PROCESSOR_ERROR | 502 |
| Rate limit exceeded | RATE_LIMITED | 429 |
9. Production VGS Setup
In production, sensitive fields are tokenized by VGS Collect JS running in the customer's browser (live environment) before they ever reach PayBridge. PayBridge operates the VGS vault and both proxy directions — you only point VGS Collect at the production vault ID and environment we issue you, then send the resulting tokens to the API.
Responsibility split
| PayBridge manages | You configure |
|---|---|
| The VGS vault and account | Load VGS Collect JS on your page |
| Inbound tokenization routes (field-name → token filters) | Initialize VGS Collect with the production vault ID + live environment |
| The outbound forward proxy that detokenizes when calling the processor | Submit the secure fields to get tokens, then POST those tokens to the PayBridge API |
| CA-certificate pinning and proxy credentials | — |
You never call the VGS forward proxy or configure detokenization — that is entirely server-side in PayBridge.
Production vault configuration
Your production VGS values are issued alongside your production API key. The values below are placeholders — substitute the real ones we provide (no real vault IDs or credentials live in this guide):
| Setting | Production value | Notes |
|---|---|---|
| Vault ID | tnt_xxxxxxxxxx | Issued with your production API key |
| Environment | live | Sandbox uses sandbox |
| VGS Collect JS | https://js.verygoodvault.com/vgs-collect/2.24.0/vgs-collect.js | Same SDK in all environments |
| Inbound route (tokenize) | https://tnt_xxxxxxxxxx.live.verygoodproxy.com | VGS Collect posts here automatically on .submit(); sandbox uses .sandbox.verygoodproxy.com |
Initialize VGS Collect with the production vault ID and live environment:
javascript
const vaultId = "tnt_xxxxxxxxxx"; // production vault ID issued by PayBridge
const env = "live"; // "sandbox" for testing
// The third argument (a state callback) is required: omitting it throws
// "callback is not a function" at init. Pass a no-op if you don't need state.
const collect = VGSCollect.create(vaultId, env, () => {});If you embed the widgets instead of building your own form, set data-vgs-vault-id="tnt_xxxxxxxxxx" and data-vgs-environment="live".
CSP requirements (add to your Content-Security-Policy header):
script-src https://js.verygoodvault.com;
frame-src https://js.verygoodvault.com;What must be tokenized
| Data | Tokenization |
|---|---|
| Card number, CVV, cardholder name | Required — handled by the checkout widget / hosted checkout (see Section 6) |
| Onboarding PII (EIN, SSN, DOB, routing number, account number) | Required in staging and production — sandbox and development also accept raw values over HTTPS (see Option B) |
Tokenizing onboarding PII keeps raw EIN/SSN/bank numbers off both your servers and ours (SAQ-A scope). The names below are the VGS Collect field names and the JSON keys you send to POST /merchants/{merchant_id}/onboard:
| Field | VGS Collect / JSON name | Raw format |
|---|---|---|
| EIN | ein | XX-XXXXXXX |
| SSN (full) | ssn | XXX-XX-XXXX (dashes optional) |
| Date of birth | dob | YYYY-MM-DD |
| Routing number | routing_number | 9 digits |
| Account number | account_number | 4–17 digits |
VGS tokens are returned in the form tok_…. ssn_last4 is display-only and is never tokenized or forwarded to the processor.
The full frontend walkthrough — creating VGS Collect instances, mounting iframe containers, and submitting to retrieve tokens — is in Option D: Custom Onboarding Form with VGS Collect. This section covers only the production configuration and the resulting payload.
Sample tokenized request
A production onboarding request with VGS tokens in place of raw PII (POST /merchants/{merchant_id}/onboard). Token values are illustrative placeholders:
json
{
"processor_type": "clearent",
"is_primary": true,
"business": {
"legal_name": "Digital Shed Builder LLC",
"dba_name": "Digital Shed Builder",
"business_type": "llc",
"ein": "tok_live_ein_a1b2c3d4e5",
"mcc_code": "5211"
},
"principal": {
"first_name": "John",
"last_name": "Builder",
"title": "Owner",
"email": "john@digitalshedbuilder.com",
"phone": "+15551234567",
"dob": "tok_live_dob_f6g7h8i9j0",
"ssn": "tok_live_ssn_k1l2m3n4o5",
"ssn_last4": "6789",
"country_of_citizenship": "US"
},
"banking": {
"routing_number": "tok_live_rout_p6q7r8s9t0",
"account_number": "tok_live_acct_u1v2w3x4y5",
"account_type": "checking",
"bank_name": "Chase"
},
"physical_address": {
"line1": "100 Commerce Blvd",
"city": "Austin",
"state_code": "TX",
"zip": "78701",
"country_code": "US"
},
"mailing_address": null
}Headers: X-API-Key: <your-production-key>, Content-Type: application/json, and X-Requested-With: PayBridge.
For the checkout (payment) path, the equivalent token fields are card_number_token / cvv_token / card_holder_token (card) and account_holder_token / account_number_token / routing_number_token (bank) — see Section 6.
10. Going Live Checklist
Before switching from sandbox to production:
- [ ] API key: Obtain your production API key from the PayBridge team.
- [ ] VGS vault ID: Switch to the production VGS vault ID and set the VGS environment to
live— see Production VGS Setup. - [ ] Base URL: Update all API calls and widget
data-api-urlfromsandbox.api.nfs-pay.comtoapi.nfs-pay.com. - [ ] CORS origins: Confirm your production domain(s) are registered.
- [ ] Webhook URL: Register your production webhook endpoint (must be HTTPS).
- [ ] Webhook handling: Verify your endpoint returns
2xxfor all event types and handles unknown events gracefully. - [ ] Idempotency: Confirm your webhook handler is idempotent (safe to receive the same event twice).
- [ ] Error handling: Verify your UI handles all error states (
PAYMENT_DECLINED,PROCESSOR_ERROR,RATE_LIMITED, network failures). - [ ] Amounts: Double-check that all amounts are in cents (minor units).
$750.00=75000. - [ ] Refund flow: Test full and partial refunds in sandbox before going live.
- [ ] Merchant onboarding: Ensure at least one merchant is fully onboarded and approved on the production processor.
- [ ] Security: Confirm API keys are stored server-side (environment variables or secrets manager), never exposed in client-side code other than widget
data-api-keyattributes.
11. Support
| Channel | Contact |
|---|---|
| Technical support | support@nfs-pay.com |
| Integration questions | Reach out to your PayBridge integration contact |
| API status | GET /health (liveness) and GET /ready (full readiness) |
| API documentation | API Reference (every environment); interactive Swagger at https://sandbox.api.nfs-pay.com/docs in sandbox |
Error Response Format
All API errors follow a consistent structure:
json
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description",
"details": {}
}
}See the API Reference for the full list of error codes.