Appearance
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-Withheader 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 theX-API-Keyheaderwebhook_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.00success_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=5000Alternative: 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
| Event | Trigger |
|---|---|
payment.completed | Payment was successfully processed |
payment.declined | Payment was declined by the processor |
payment.refunded | A refund was processed |
checkout.completed | A hosted checkout session payment succeeded |
merchant.approved / merchant.boarded | Onboarding application approved / merchant went live |
merchant.pended / merchant.manual_review / merchant.rejected | Application 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 — noX-Requested-Withheader 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:
amountis in minor units (cents). Omit for full refund.idempotency_keyis required to prevent duplicate refunds.- Multiple partial refunds are allowed up to the original payment amount.
- Only payments in
postedorsettledstatus 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 Pattern | Auth Header | Description |
|---|---|---|
/admin/auth/login | None | Admin 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/aptexx | Processor signature headers | Processor callbacks |
State-changing requests (POST, PUT, PATCH, DELETE) on consumer-app and customer routes require the CSRF header:
X-Requested-With: PayBridgeAdmin Bearer-authenticated endpoints are CSRF-exempt.
Rate Limits
| Endpoint Category | Limit |
|---|---|
Payments (POST /payments) | 10 requests/minute |
| Refunds | 5 requests/minute |
| Other mutations (POST/PUT/PATCH/DELETE) | 20 requests/minute |
| Admin | 30 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": {}
}
}| Status | Code | Action |
|---|---|---|
| 400 | ONBOARDING_ERROR, BAD_REQUEST | Check message for specifics |
| 401 | UNAUTHORIZED | Verify your X-API-Key or JWT token |
| 402 | PAYMENT_DECLINED | Customer should try another payment method |
| 404 | RESOURCE_NOT_FOUND | Verify the UUID and merchant ownership |
| 409 | IDEMPOTENCY_CONFLICT | Check if the operation already succeeded |
| 422 | VALIDATION_ERROR | Check request body against the schema |
| 429 | RATE_LIMITED | Retry after the Retry-After header period |
| 502 | PROCESSOR_TRANSIENT_ERROR | Safe to retry with the same idempotency key |
Next Steps
- Widget Integration Guide -- embed the payment form directly in your page
- API Reference -- full endpoint documentation