Skip to content

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.rejected webhooks with polling fallback
  • hosted checkout via POST /checkout/sessions

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 app

Sensitive 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:

ItemHow to get it
API KeyProvisioned by the PayBridge team. This is a X-API-Key value that identifies your consumer app.
VGS Vault IDProvided alongside your API key. Required for checkout and onboarding widgets.
Webhook endpointA publicly reachable HTTPS URL on your server to receive event notifications.
Sandbox base URLhttps://sandbox.api.nfs-pay.com
Production base URLhttps://api.shedpay.us

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 for https://js.verygoodvault.com on the /boarding/echo path 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_here

The 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 typeLimit
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 endpoints200 requests / 60 seconds
Admin endpoints30 requests / 60 seconds

Rate limit headers are included in every response:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97

When 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:00"
}

Save the id -- this is your merchant_id.

Step 2: Onboard the merchant with a processor ​

Submit the full application with POST /merchants/{merchant_id}/onboard — the request body and field reference are in Section 5. The response is 202 with the processor record in submitted status.

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. If a sandbox merchant stalls, ask your integration contact to approve it: the PayBridge team can flip a sandbox processor to approved from the admin side (your API key cannot), which fires the merchant.approved webhook to your app, so 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:00",
      "approved_at": "2026-03-10T14:30:00"
    }
  ]
}

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:00"
}

customer_id is your own ID for the customer, whatever your system already calls them. PayBridge keeps the customer's record at the processor for you, so you never need a processor customer key. Saving another card or bank account with the same customer_id adds it to the same customer.

The id in the response is what you charge: pass it as payment_method_id to POST /payments for this merchant (Step 5). A method saved this way can be charged only by your own merchants.

Note: The card_number_token, cvv_token, and card_holder_token values 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:00",
  "created_at": "2026-03-10T12:15:00"
}

The amount field is always in minor units (cents). 75000 = $750.00.


5. Merchant Onboarding ​

Submits all merchant details directly via the PayBridge API, triggering a 16-step processor onboarding sequence. This is the only supported onboarding path: you collect merchant data in your own UI, so the experience stays branded and the merchant stays attributed to you.

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": "000123456789",
    "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:

FieldTypeRequiredDescription
legal_namestringYesLegal business name
dba_namestringYesDoing Business As name
business_typestringYesEntity type: sole_proprietorship, partnership, corporation, llc, non_profit, government, or association_estate_trust. Any other value is rejected with HTTP 422.
einstringYesTax ID / EIN (raw XX-XXXXXXX or VGS token)
mcc_codestringNo (default: "6513")4-digit Merchant Category Code
websitestringNoBusiness website URL
annual_volumeintNoEstimated annual sales volume in cents
average_ticketintNoAverage transaction amount in cents
high_ticketintNoHighest expected transaction in cents
card_present_percentageintNoCard-present percentage (0-100)

Principal fields:

FieldTypeRequiredDescription
first_namestringYesFirst name
last_namestringYesLast name
titlestringYesJob title (Owner, CEO, etc.)
emailstringYesEmail address
phonestringYesPhone number
dobstringNoDate of birth (raw YYYY-MM-DD or VGS token)
ssnstringYes for ClearentFull SSN (raw XXX-XX-XXXX with optional dashes, or VGS token) — forwarded to processors as Contact.LegalID
ssn_last4stringNoLast 4 digits of SSN — display only, not forwarded to processors
country_of_citizenshipstringNo (default: "US")Country code

Banking fields:

FieldTypeRequiredDescription
routing_numberstringYes9-digit ABA routing number (raw or VGS token)
account_numberstringYesAccount number (raw or VGS token)
account_typestringNo (default: "checking")checking or savings
bank_namestringNoName of the bank

Address fields (physical_address and optional mailing_address):

FieldTypeRequiredDescription
line1stringYesStreet address line 1
line2stringNoSuite, apt, etc.
citystringYesCity
state_codestringYes2-character state code
zipstringYesZIP / postal code (5+ chars)
country_codestringNo (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 retryable failures (status draft) 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 draft to 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.shedpay.us"
     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.shedpay.us/widget/paybridge-widget.umd.js"></script>

Widget data-* attributes:

AttributeRequiredDescription
data-partnerNoYour referral partner ID for revenue attribution
data-api-urlYesPayBridge API base URL
data-api-keyYesYour consumer app API key
data-processor-typeNo (default: "clearent")Processor type
data-vgs-vault-idYesVGS vault ID for secure bank fields
data-vgs-environmentNo (default: "sandbox")sandbox or live
data-redirectNoURL to redirect to after approval
data-terms-urlNoTerms of Service URL (shown on review step)
data-privacy-urlNoPrivacy Policy URL (shown on review step)
data-support-emailNoSupport email displayed on confirmation screen
data-status-callbackNoJavaScript 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:

FieldVGS field nameValidation
EINein/^\d{2}-?\d{7}$/
SSN (full)ssn/^\d{3}-?\d{2}-?\d{4}$/
Date of birthdob/^\d{4}-\d{2}-\d{2}$/
Routing numberrouting_number/^\d{9}$/
Account numberaccount_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("/boarding/echo", {}, (status, data) => { // the vault forwards here and PayBridge returns the tokens
      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:

StatusMeaning
draftA boarding step failed partway. Recoverable — re-submit POST /merchants/{id}/onboard to resume from where it stopped (see Resuming a failed onboarding).
submittedApplication submitted, awaiting review
pending_reviewUnder review by the processor
approvedMerchant account is active and can process payments
rejectedApplication 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.shedpay.us"
     data-widget-token="wt1..."
     data-on-uploaded="onDocUploaded"
     data-on-error="onDocError">
</div>

<script src="https://api.shedpay.us/widget/paybridge-widget.umd.js"></script>

Widget data-* attributes:

AttributeRequiredDescription
data-api-urlYesPayBridge API base URL
data-widget-tokenYesShort-lived widget token granted the upload_documents action (from POST /widget/sessions)
data-on-uploadedNowindow-level JS callback name, invoked with { document_id } after a successful upload
data-on-errorNowindow-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 ​

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.shedpay.us"
     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.shedpay.us/widget/paybridge-widget.umd.js"></script>

Widget data-* attributes:

AttributeRequiredDescription
data-merchantYesMerchant ID (from Step 1)
data-amountYesAmount in cents (e.g., 75000 = $750.00)
data-currencyNo (default: "USD")ISO 4217 currency code
data-api-urlYesPayBridge API base URL
data-api-keyYesYour consumer app API key
data-vgs-vault-idYesVGS vault ID for secure card/bank fields
data-vgs-environmentNo (default: "sandbox")sandbox or live
data-redirectNoURL to redirect to after successful payment
data-on-successNoJavaScript function name on window for success callback
data-on-errorNoJavaScript 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 ID
  • pay_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, and data: URIs are blocked. The data-redirect URL must be same-origin as the page embedding the widget or match the data-api-url origin.

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"
  }
}
FieldTypeRequiredDescription
customer_idstringYesYour own ID for the customer (up to 255 characters). Every method saved with the same ID belongs to the same customer
card_number_tokenstringYesVGS token for card number
cvv_tokenstringYesVGS token for CVV
card_holder_tokenstringYesVGS token for cardholder name
expires_atstringYesExpiry in YYYY-MM format
zipstringYesBilling ZIP code
billing_addressobjectNoFull billing address
last_fourstringNoLast four digits of the card, as VGS Collect's card number field reports them (its last4 state). Send it so the saved card shows its last four where the processor does not report them, as on Aptexx; where the processor does, its value is kept instead

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"
}
FieldTypeRequiredDescription
customer_idstringYesYour own ID for the customer (up to 255 characters). Every method saved with the same ID belongs to the same customer
account_holder_tokenstringYesVGS token for account holder name
account_number_tokenstringYesVGS token for account number
routing_number_tokenstringYesVGS token for routing number
account_typestringNo (default: "checking")checking or savings

Both save endpoints return the saved method, and its id is the payment_method_id you charge at this merchant. Saving again with a customer_id you have used before adds the new card or bank account to that same customer rather than creating another one.

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"
  }
}
FieldTypeRequiredDescription
merchant_idstringYesMerchant ID
amountintYesAmount in cents
currencystringNo (default: "USD")ISO 4217 currency code
payment_method_idstringYes*Payment method ID (from add card/bank)
idempotency_keystringYesUnique key for this request (prevents double-charges on retry). Use your order ID or a UUID. Max 255 chars.
descriptionstringNoPayment description
invoice_numberstringNoInvoice reference
customerobjectNoCustomer info (first_name, last_name, email)
billing_addressobjectNoBilling address
metadataobjectNoArbitrary key-value pairs for your records
customer_emailstringNoCustomer email (for portable methods)
customer_payment_method_idstringNo*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).

Expired cards. A card is good through the last day of its expiry month (UTC). Charging a saved card after that, by payment_method_id or customer_payment_method_id, returns 422 with the code CARD_EXPIRED, and PayBridge does not contact the processor. Saving a card that has already expired returns the same error. Ask the customer for another card.

Payment statuses:

StatusMeaning
postedPayment completed successfully
declinedDeclined by processor
failedTransient processor error
refundedPayment 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"
}
FieldTypeRequiredDescription
amountintNoRefund amount in cents. Omit for full refund.
idempotency_keystringYesUnique key for this refund request (prevents duplicate refunds on retry).
descriptionstringNoReason 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.

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",
    "idempotency_key": "order-12345"
  }'

Response (201):

json
{
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "checkout_url": "https://api.shedpay.us/checkout/session/f47ac10b-58cc-4372-a567-0e02b2c3d479?st=v1.1710168600.abc123def456...",
  "amount": 75000,
  "currency": "USD",
  "status": "pending",
  "expires_at": "2026-03-11T16:00:00",
  "created_at": "2026-03-11T15:00:00"
}

Fields:

FieldTypeRequiredDescription
merchant_idstringYesMerchant ID
amountintYesAmount in cents (min 1, max 99999999)
currencystringNoDefault "USD"
success_urlstringYesWhere to redirect after successful payment
cancel_urlstringNoWhere to redirect on cancellation
descriptionstringNoPayment description (max 500 chars)
customer_emailstringNoPre-fill customer email on checkout page
idempotency_keystringYesPrevents duplicate sessions on retry; resending a key returns the original session

Security: success_url and cancel_url must match your app's registered cors_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)
  • Saving a card or bank account to the customer's account, and paying with a card they saved before, after a one-time code emailed to them. Paying with a saved card at a merchant for the first time approves that merchant to charge it
  • Payment submission
  • Error display and retry
  • Redirect to your success_url on success or cancel_url on 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=success

Step 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-11T16:00:00",
  "completed_at": "2026-03-11T15:05:00"
}

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 60 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:00",
      "created_at": "2026-03-11T15:00:00"
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0
}

Query parameters:

ParamTypeDefaultDescription
statusstring—Filter by status (pending, posted, declined, refunded, etc.)
limitint50Page size (1–100)
offsetint0Number 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:00"
  }
]

7. Webhooks ​

PayBridge forwards processor events to your registered webhook URL as standardized JSON payloads.

Event Types ​

Payment events (forwarded to your webhook URL):

EventTrigger
payment.completedPayment settled or approved by processor
payment.declinedPayment declined by processor
payment.refundedRefund processed

Merchant events (forwarded to your webhook URL):

EventTrigger
merchant.approvedMerchant onboarding application approved
merchant.rejectedMerchant onboarding application rejected

Checkout events (forwarded to your webhook URL):

EventTrigger
checkout.completedHosted 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_id field 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 within 10 seconds. Delivery is best-effort: a network error or timeout is recorded and retried by the PayBridge team on a backoff schedule, while a non-2xx response counts as delivered and is not retried (see Retry Behavior). For guaranteed delivery, poll the status APIs as a fallback.

Setting Your Webhook URL ​

Your webhook URL is registered by the PayBridge team. Setting or changing it is an admin-only operation (PUT /admin/apps/{app_id}/webhook-url), so your API key cannot do it: send your integration contact the HTTPS URL you want for each environment — sandbox and production are registered separately. Once it is registered, call POST /webhooks/test with your API key to confirm delivery; the response shows the webhook_url on file.

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 (issued by the PayBridge team with your API key).

Verification steps:

  1. Read the raw request body bytes (do not parse JSON first).
  2. Compute HMAC-SHA256 of the body using your webhook_secret.
  3. Compare your computed signature with the X-PayBridge-Signature header using a constant-time comparison.
  4. 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_id in 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 ​

ResourceURL
APIhttps://sandbox.api.nfs-pay.com
API Docs (Swagger, sandbox only)https://sandbox.api.nfs-pay.com/docs
Widget JShttps://sandbox.api.nfs-pay.com/widget/paybridge-widget.umd.js
VGS Environmentsandbox

Use your sandbox API key for all test requests. Sandbox transactions do not hit real processors.

Test Scenarios ​

Checkout widget:

  1. Embed the checkout widget with data-vgs-environment="sandbox" and your sandbox VGS vault ID.
  2. Fill in test card details in the VGS Collect iframes.
  3. Verify the success callback fires and the redirect includes pay_txn_id.
  4. Test error handling: use an invalid amount (0 or negative) to trigger a validation error.

Onboarding widget:

  1. Embed the onboarding widget with sandbox credentials.
  2. Walk through all four steps (Business Info, Owner Info, Banking, Review).
  3. Verify the status polling screen appears after submission.
  4. Confirm the data-status-callback function is called with { status: "submitted", merchantId: "..." }.

API direct:

  1. Create a merchant via POST /merchants.
  2. Submit onboarding via POST /merchants/{id}/onboard.
  3. Poll GET /merchants/{id}/onboard/status until status changes.
  4. Add a payment method and process a test payment.
  5. Issue a refund.

Error Codes to Test Against ​

ScenarioExpected Error CodeHTTP Status
Missing API keyVALIDATION_ERROR422
Invalid API keyINVALID_API_KEY401
Merchant not foundMERCHANT_NOT_FOUND404
Invalid request bodyVALIDATION_ERROR422
Payment declinedPAYMENT_DECLINED402
Saved card past its expiry monthCARD_EXPIRED422
Processor errorPROCESSOR_ERROR502
Rate limit exceededRATE_LIMITED429

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 managesYou configure
The VGS vault and accountLoad 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 processorSubmit 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):

SettingProduction valueNotes
Vault IDtnt_xxxxxxxxxxIssued with your production API key
EnvironmentliveSandbox uses sandbox
VGS Collect JShttps://js.verygoodvault.com/vgs-collect/2.24.0/vgs-collect.jsSame SDK in all environments
Inbound route (tokenize)https://tnt_xxxxxxxxxx.live.verygoodproxy.comVGS 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 ​

DataTokenization
Card number, CVV, cardholder nameRequired — 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 Merchant Onboarding)

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:

FieldVGS Collect / JSON nameRaw format
EINeinXX-XXXXXXX
SSN (full)ssnXXX-XX-XXXX (dashes optional)
Date of birthdobYYYY-MM-DD
Routing numberrouting_number9 digits
Account numberaccount_number4–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-url from sandbox.api.nfs-pay.com to api.shedpay.us.
  • [ ] CORS origins: Confirm your production domain(s) are registered.
  • [ ] Webhook URL: Send your production HTTPS webhook URL to your integration contact to register (see Setting Your Webhook URL), then confirm it with POST /webhooks/test.
  • [ ] Webhook handling: Verify your endpoint returns 2xx for 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-key attributes.

11. Support ​

ChannelContact
Technical supportReach out to your PayBridge integration contact
API statusGET /health (liveness) and GET /ready (full readiness)
API documentationAPI 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.