Skip to content

Getting Started ​

Step-by-step guide for integrating with PayBridge. By the end of this guide, you will be able to create merchants, accept payments via hosted checkout, and handle webhooks.

Prerequisites ​

  • Admin credentials (email + password) for the PayBridge admin portal
  • A server-side environment for API calls (Node.js, Python, PHP, etc.)
  • HTTPS-enabled domain for production use

1. Authenticate as Admin ​

Log in with your admin credentials to get a JWT token. This token is used for all admin operations (creating consumer apps, managing merchants, etc.).

bash
curl -X POST https://api.nfs-pay.com/admin/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@yourcompany.com",
    "password": "your-admin-password"
  }'

Response (200 OK):

json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "admin": {
    "id": "a1b2c3d4-...",
    "name": "Admin User",
    "email": "admin@yourcompany.com",
    "role": "admin"
  }
}

Use the access_token in all subsequent admin requests:

Authorization: Bearer <access_token>

Tokens expire after 60 minutes. Refresh with POST /admin/auth/refresh before expiry.

2. Create a Consumer App ​

Create your consumer app to get an API key. The API key is returned once at creation -- store it securely.

bash
curl -X POST https://api.nfs-pay.com/admin/apps \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Application",
    "webhook_url": "https://yoursite.com/webhooks/pb",
    "cors_origins": [
      "https://yoursite.com",
      "https://www.yoursite.com"
    ]
  }'

Admin Bearer endpoints are CSRF-exempt — no X-Requested-With header needed.

Response (201 Created):

json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "My Application",
  "api_key": "pb_live_abc123...xyz789",
  "webhook_secret": "whsec_abc123...xyz789",
  "created_at": "2026-03-13T10:00:00Z"
}

Save both api_key and webhook_secret securely. You will need:

  • api_key -- for all API calls via the X-API-Key header
  • webhook_secret -- to verify incoming webhook signatures

3. Create a Merchant ​

Register a merchant (the business that will accept payments). All subsequent operations reference this merchant by UUID.

bash
curl -X POST https://api.nfs-pay.com/merchants \
  -H "X-API-Key: pb_live_abc123...xyz789" \
  -H "Content-Type: application/json" \
  -H "X-Requested-With: PayBridge" \
  -d '{
    "name": "Acme Widgets",
    "business_name": "Acme Widgets LLC",
    "email": "billing@acmewidgets.com",
    "business_type": "llc"
  }'

Response (201 Created):

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Acme Widgets",
  "business_name": "Acme Widgets LLC",
  "status": "active",
  "referral_partner_id": null,
  "created_at": "2026-03-13T10:01:00Z"
}

4. Onboard the Merchant with a Processor ​

Before a merchant can accept payments, they need a payment processor account. The supported path is programmatic onboarding: collect the merchant's details in your own UI and submit them in a single POST /merchants/{merchant_id}/onboard call, and PayBridge forwards everything to the processor's multi-step onboarding API on your behalf. The full request and field reference — including ownership_percentage and multi-owner additional_owners — is in the API Reference; the Integration Guide §5 (Merchant Onboarding) covers the end-to-end flow.

bash
curl -X POST https://api.nfs-pay.com/merchants/550e8400-e29b-41d4-a716-446655440000/onboard \
  -H "X-API-Key: pb_live_abc123...xyz789" \
  -H "Content-Type: application/json" \
  -H "X-Requested-With: PayBridge" \
  -d '{
    "processor_type": "clearent",
    "is_primary": true,
    "business": {
      "legal_name": "Acme Widgets LLC",
      "dba_name": "Acme Widgets",
      "business_type": "llc",
      "ein": "27-1234567",
      "mcc_code": "5999",
      "website": "https://acmewidgets.com",
      "annual_volume": 50000000,
      "average_ticket": 75000
    },
    "principal": {
      "first_name": "Jane",
      "last_name": "Owner",
      "title": "Owner",
      "email": "jane@acmewidgets.com",
      "phone": "512-555-1234",
      "dob": "1985-06-15",
      "ssn": "123-45-6789",
      "ssn_last4": "6789",
      "country_of_citizenship": "US",
      "ownership_percentage": 100
    },
    "banking": {
      "routing_number": "021000021",
      "account_number": "123456789",
      "account_type": "checking",
      "bank_name": "Chase"
    },
    "physical_address": {
      "line1": "100 Commerce Blvd",
      "city": "Austin",
      "state_code": "TX",
      "zip": "78701",
      "country_code": "US"
    }
  }'

Response (202 Accepted):

json
{
  "processor_id": "660e8400-e29b-41d4-a716-446655440001",
  "type": "clearent",
  "onboarding_status": "submitted",
  "processor_merchant_id": "CLR-12345",
  "connected": false,
  "resumed": false,
  "submitted_at": "2026-03-13T10:02:00Z"
}

Sensitive values. In sandbox, send ein, dob, ssn, routing_number and account_number as plain values, exactly as shown above — the example runs as-is. Staging and production require these fields to be genuine VGS tokens and reject raw values. Do not substitute placeholder strings shaped like tokens (tok_…): any value in that shape is treated as a real VGS token and routed to the tokenization proxy, which fails.

If a step fails you get a 400 naming the failed_step; fix that field and re-POST the same request — onboarding resumes from where it stopped rather than starting over. A field that fails schema validation instead returns a 422 carrying the offending loc/msg (no failed_step, and no draft to resume). Identity fields (legal name, DBA, business type, EIN, owner SSNs) are frozen once a draft exists — changing one on a resubmit returns 409 FROZEN_IDENTITY_CHANGED; see Integration Guide §5 — Resuming a failed onboarding.

Alternative: hosted onboarding (not the supported path)

POST /merchants/{id}/onboard/hosted hands the application off to the processor's own form and returns an application_url for the merchant to complete. It is not a currently supported path — it has not been verified against Clearent, and the application_url host is illustrative, not a confirmed hosted-apply host (AB#8247). Prefer the programmatic flow above.

bash
curl -X POST https://api.nfs-pay.com/merchants/550e8400-e29b-41d4-a716-446655440000/onboard/hosted \
  -H "X-API-Key: pb_live_abc123...xyz789" \
  -H "Content-Type: application/json" \
  -H "X-Requested-With: PayBridge" \
  -d '{
    "processor_type": "clearent",
    "is_primary": true,
    "dba_name": "Acme Widgets",
    "email": "billing@acmewidgets.com",
    "mcc_code": "5999"
  }'

Check Onboarding Status ​

Poll the status endpoint or wait for a webhook:

bash
curl https://api.nfs-pay.com/merchants/550e8400-e29b-41d4-a716-446655440000/onboard/status \
  -H "X-API-Key: pb_live_abc123...xyz789"

Response:

json
{
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000",
  "processors": [
    {
      "processor_id": "660e8400-e29b-41d4-a716-446655440001",
      "type": "clearent",
      "onboarding_status": "approved",
      "processor_merchant_id": "CLR-12345",
      "submitted_at": "2026-03-13T10:02:00Z",
      "approved_at": "2026-03-14T15:30:00Z",
      "rejection_reason": null
    }
  ]
}

Status values: pending, submitted, pending_review, approved, rejected, draft.

Once onboarding_status is approved, the merchant can accept payments.

5. Create a Checkout Session ​

Create a checkout session server-to-server. The customer is then redirected to the hosted checkout page.

bash
curl -X POST https://api.nfs-pay.com/checkout/sessions \
  -H "X-API-Key: pb_live_abc123...xyz789" \
  -H "Content-Type: application/json" \
  -H "X-Requested-With: PayBridge" \
  -d '{
    "merchant_id": "550e8400-e29b-41d4-a716-446655440000",
    "amount": 5000,
    "currency": "USD",
    "success_url": "https://yoursite.com/payment/success",
    "cancel_url": "https://yoursite.com/payment/cancel",
    "description": "Order #12345",
    "customer_email": "customer@example.com",
    "idempotency_key": "order-12345"
  }'

Response (201 Created):

json
{
  "id": "770e8400-e29b-41d4-a716-446655440002",
  "checkout_url": "https://api.nfs-pay.com/checkout/session/770e8400-e29b-41d4-a716-446655440002?st=v1.1710331200.abc123...",
  "amount": 5000,
  "currency": "USD",
  "status": "pending",
  "expires_at": "2026-03-13T11:30:00Z",
  "created_at": "2026-03-13T10:30:00Z"
}

Key Fields ​

  • amount: Amount in minor units (cents). 5000 = $50.00
  • success_url: Where the customer lands after successful payment. Must match your app's registered CORS origins.
  • cancel_url: Where the customer lands if they cancel (optional).
  • idempotency_key: Prevents duplicate sessions on network retry. Must be unique per session.

6. Redirect the Customer ​

Send the customer to checkout_url from the response. This loads the hosted checkout page where they enter card or bank details securely.

html
<!-- Server-rendered redirect -->
<a href="https://api.nfs-pay.com/checkout/session/770e8400-...?st=v1.1710331200.abc123...">
  Pay $50.00
</a>

Or redirect programmatically:

js
// After creating the session via your backend
window.location.href = checkoutSession.checkout_url;

After payment, the customer is redirected to your success_url with query parameters:

https://yoursite.com/payment/success?pay_session_id=770e8400-...&pay_payment_id=880e8400-...&pay_status=posted&pay_amount=5000

Alternative: Embed the Widget ​

Instead of redirecting, you can embed the checkout widget directly in your page. See Widget Integration Guide for details.

7. Handle Webhooks ​

PayBridge sends signed webhooks to the URL registered with your consumer app. Webhooks include an X-PayBridge-Signature header for verification.

Webhook Events ​

EventTrigger
payment.completedPayment was successfully processed
payment.declinedPayment was declined by the processor
payment.refundedA refund was processed
checkout.completedA hosted checkout session payment succeeded
merchant.approved / merchant.boardedOnboarding application approved / merchant went live
merchant.pended / merchant.manual_review / merchant.rejectedApplication pended, sent to manual review, or denied
document.*The processor requested or reviewed an underwriting document

See the Webhook guide for the complete, authoritative event catalog, envelope shapes, and retry behavior.

Verifying Signatures ​

Every webhook includes:

  • X-PayBridge-Signature: HMAC-SHA256 signature of the request body (format: sha256=<hex>)

Timestamp freshness is validated from the JSON payload body (not HTTP headers). The server checks for timestamp, created, or eventDate fields in the payload and rejects stale events (default max age: 300 seconds / 5 minutes). Accepted timestamp formats: Unix epoch (int/float) or ISO 8601 strings.

Verify the signature using your webhook_secret:

python
import hashlib
import hmac
import json

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)
javascript
const crypto = require("crypto");

function verifyWebhook(body, signature, secret) {
  const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");
  return `sha256=${expected}` === signature;
}

Example Webhook Payload ​

json
{
  "event_type": "checkout.completed",
  "webhook_id": "990e8400-e29b-41d4-a716-446655440003",
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000",
  "payment_id": "880e8400-e29b-41d4-a716-446655440004",
  "amount": 5000,
  "currency": "USD",
  "status": "posted",
  "timestamp": "2026-03-13T10:31:00Z"
}

Test Your Webhook Handler ​

Before going live, verify your handler works:

bash
curl -X POST https://api.nfs-pay.com/webhooks/test \
  -H "X-API-Key: pb_live_abc123...xyz789"

/webhooks/* is server-to-server and CSRF-exempt — no X-Requested-With header needed.

Response:

json
{
  "delivered": true,
  "status_code": 200,
  "error": null,
  "webhook_url": "https://yoursite.com/webhooks/pb"
}

8. Process Refunds ​

Issue a full or partial refund against a completed payment:

bash
# Full refund
curl -X POST https://api.nfs-pay.com/payments/880e8400-e29b-41d4-a716-446655440004/refund \
  -H "X-API-Key: pb_live_abc123...xyz789" \
  -H "Content-Type: application/json" \
  -H "X-Requested-With: PayBridge" \
  -d '{
    "idempotency_key": "refund-order-12345",
    "description": "Customer requested cancellation"
  }'
bash
# Partial refund ($20.00 of a $50.00 payment)
curl -X POST https://api.nfs-pay.com/payments/880e8400-e29b-41d4-a716-446655440004/refund \
  -H "X-API-Key: pb_live_abc123...xyz789" \
  -H "Content-Type: application/json" \
  -H "X-Requested-With: PayBridge" \
  -d '{
    "amount": 2000,
    "idempotency_key": "partial-refund-order-12345",
    "description": "Partial refund for returned item"
  }'

Response (201 Created):

json
{
  "id": "aa0e8400-e29b-41d4-a716-446655440005",
  "payment_id": "880e8400-e29b-41d4-a716-446655440004",
  "amount": 2000,
  "status": "posted",
  "processor_type": "clearent",
  "description": "Partial refund for returned item",
  "created_at": "2026-03-13T11:00:00Z"
}

Refund rules:

  • amount is in minor units (cents). Omit for full refund.
  • idempotency_key is required to prevent duplicate refunds.
  • Multiple partial refunds are allowed up to the original payment amount.
  • Only payments in posted or settled status can be refunded.

9. Verify Payment Status ​

After the customer returns to your success_url, confirm the checkout session is complete:

bash
curl https://api.nfs-pay.com/checkout/sessions/770e8400-e29b-41d4-a716-446655440002 \
  -H "X-API-Key: pb_live_abc123...xyz789"

Response:

json
{
  "id": "770e8400-e29b-41d4-a716-446655440002",
  "status": "completed",
  "amount": 5000,
  "currency": "USD",
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000",
  "description": "Order #12345",
  "payment_id": "880e8400-e29b-41d4-a716-446655440004",
  "expires_at": "2026-03-13T11:30:00Z",
  "completed_at": "2026-03-13T10:31:00Z",
  "created_at": "2026-03-13T10:30:00Z"
}

Always verify status is "completed" server-side. Do not rely solely on the redirect URL parameters.

Authentication Summary ​

Endpoint PatternAuth HeaderDescription
/admin/auth/loginNoneAdmin login (returns JWT)
/admin/*Authorization: Bearer <JWT>Admin operations (JWT from login)
/merchants, /payments, /checkout/*X-API-Key: <API_KEY>Consumer app API calls
/customer/* (except auth)Authorization: Bearer <JWT>Customer self-service (JWT from magic link)
/webhooks/clearent, /webhooks/aptexxProcessor signature headersProcessor callbacks

State-changing requests (POST, PUT, PATCH, DELETE) on consumer-app and customer routes require the CSRF header:

X-Requested-With: PayBridge

Admin Bearer-authenticated endpoints are CSRF-exempt.

Rate Limits ​

Endpoint CategoryLimit
Payments (POST /payments)10 requests/minute
Refunds5 requests/minute
Other mutations (POST/PUT/PATCH/DELETE)20 requests/minute
Admin30 requests/minute
Webhooks (processor callbacks)200 requests/minute
Reads (GET)100 requests/minute

When rate-limited, you receive HTTP 429 with a Retry-After header.

Error Handling ​

All errors return a consistent JSON structure:

json
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Merchant not found",
    "details": {}
  }
}
StatusCodeAction
400ONBOARDING_ERROR, BAD_REQUESTCheck message for specifics
401UNAUTHORIZEDVerify your X-API-Key or JWT token
402PAYMENT_DECLINEDCustomer should try another payment method
404RESOURCE_NOT_FOUNDVerify the UUID and merchant ownership
409IDEMPOTENCY_CONFLICTCheck if the operation already succeeded
422VALIDATION_ERRORCheck request body against the schema
429RATE_LIMITEDRetry after the Retry-After header period
502PROCESSOR_TRANSIENT_ERRORSafe to retry with the same idempotency key

Next Steps ​