Skip to content

Quickstart: Hosted Merchant Onboarding (HMO) ​

This guide covers everything you need to onboard merchants onto a payment processor through PayBridge's hosted onboarding flow. In HMO, PayBridge creates a hosted application at the processor and returns a URL — the merchant completes the form directly with the processor.

Note: Hosted onboarding is not a currently supported path — it has not been verified against Clearent, and the application_url host shown in the examples below (boarding.clearent.net) is illustrative, not a confirmed hosted-apply host. Use the programmatic onboarding flow (POST /merchants/{id}/onboard) instead — see the Integration Guide §5 (Merchant Onboarding) for the full request and field reference. Tracked in AB#8247.

Prerequisites ​

  • A PayBridge API key (provided during consumer app setup)
  • A PayBridge webhook secret (provided during consumer app setup)
  • An HTTPS endpoint on your server to receive webhooks

If you don't have these yet, contact the PayBridge team or ask your admin to create a consumer app via POST /admin/apps.


The Flow ​

Your App                    PayBridge API              Processor (Clearent)
--------                    -----------------              --------------------
    |                              |                              |
    |-- 1. POST /merchants ------->|                              |
    |<-- merchant_id --------------|                              |
    |                              |                              |
    |-- 2. POST /merchants/        |                              |
    |   {id}/onboard/hosted ------>|-- creates hosted session --->|
    |<-- application_url ----------|<-- merchant number ----------|
    |                              |                              |
    |-- 3. Redirect merchant       |                              |
    |   to application_url --------|----------------------------->|
    |                              |                              |
    |   (Merchant completes form)  |                              |
    |                              |                              |
    |                              |<-- merchant.approved --------|
    |<-- 4. Webhook: -------------|    (processor callback)       |
    |   merchant.approved          |                              |
    |                              |                              |
    |-- 5. GET /merchants/         |                              |
    |   {id}/onboard/status ------>|                              |
    |<-- status: approved ---------|                              |

Step 1: Register a Merchant ​

Create a merchant record in PayBridge. This merchant will later be onboarded with a processor.

bash
curl -X POST https://api.nfs-pay.com/merchants \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Requested-With: PayBridge" \
  -d '{
    "name": "Acme Property Management",
    "business_name": "Acme Property Management LLC",
    "business_type": "llc",
    "email": "payments@acmepm.com",
    "phone": "5551234567",
    "address": {
      "street": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    }
  }'

Response 201 Created:

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Acme Property Management",
  "business_name": "Acme Property Management LLC",
  "status": "active",
  "processors": [],
  "referral_partner_id": null,
  "created_at": "2026-03-25T10:00:00Z"
}

Save the id — you'll use it in every subsequent call.

Required Fields ​

FieldTypeDescription
namestringDisplay name
business_namestringLegal business name
business_typestringEntity type: llc, corporation, sole_proprietorship, partnership, non_profit, government or association_estate_trust. Any other value is rejected.
emailstringPrimary contact email

Optional Fields ​

FieldTypeDescription
einstringEmployer Identification Number (can also be collected during onboarding)
phonestringContact phone number
addressobjectBusiness address (street, city, state, zip, country)
referral_partner_idstringYour partner ID for kickback revenue tracking
referral_codestringReferral code used during signup

Step 2: Initiate Hosted Onboarding ​

Submit the merchant for hosted onboarding. PayBridge creates a hosted application at the processor and returns the application_url.

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

Response 202 Accepted:

json
{
  "processor_id": "660e8400-e29b-41d4-a716-446655440001",
  "type": "clearent",
  "onboarding_status": "submitted",
  "processor_merchant_id": "100209766",
  "application_url": "https://boarding.clearent.net/app/...",
  "submitted_at": "2026-03-25T10:01:00Z"
}

Request Fields ​

FieldTypeRequiredDefaultDescription
processor_typestringNo"clearent"Payment processor to onboard with
is_primaryboolNotrueSet as primary processor for this merchant
dba_namestringYes—"Doing Business As" name
emailstringYes—Merchant email (receives processor communications)
mcc_codestringNo"6513"4-digit Merchant Category Code

Common MCC Codes ​

CodeCategory
6513Real estate / property management
5999General retail
7299General services
1520Contractors / construction
7311Advertising services

Step 3: Direct the Merchant to the Application ​

Redirect or link the merchant to the application_url from the response. The merchant completes the onboarding form directly with the processor — PayBridge is not involved in this step.

html
<!-- In your app's merchant management UI -->
<a href="https://boarding.clearent.net/app/..."
   target="_blank">
  Complete Payment Processing Application
</a>

Or send the URL via email to the merchant.

What happens next:

  • The merchant fills out the processor's application form (business details, banking, KYC)
  • The processor reviews the application (typically 1–3 business days)
  • PayBridge receives a webhook from the processor when the application is approved or rejected
  • PayBridge forwards a standardized webhook to your registered webhook URL

Step 4: Handle Webhooks ​

When the processor makes a decision, PayBridge forwards a webhook to your registered endpoint.

Webhook Events ​

EventMeaning
merchant.approvedApplication approved — merchant can accept payments
merchant.rejectedApplication denied — check rejection_reason

Example: merchant.approved ​

json
{
  "event_type": "merchant.approved",
  "webhook_id": "990e8400-e29b-41d4-a716-446655440003",
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000",
  "processor_type": "clearent",
  "processor_merchant_id": "100209766",
  "onboarding_status": "approved",
  "timestamp": "2026-03-27T14:30:00Z"
}

Example: merchant.rejected ​

json
{
  "event_type": "merchant.rejected",
  "webhook_id": "990e8400-e29b-41d4-a716-446655440004",
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000",
  "processor_type": "clearent",
  "processor_merchant_id": "100209766",
  "onboarding_status": "rejected",
  "rejection_reason": "Incomplete banking information",
  "timestamp": "2026-03-27T14:30:00Z"
}

Verifying Webhook Signatures ​

Every webhook includes an X-PayBridge-Signature header. Verify it using your webhook_secret:

Python:

python
import hashlib
import hmac

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)

# In your webhook handler:
body = request.body          # raw bytes
sig = request.headers["X-PayBridge-Signature"]
if not verify_webhook(body, sig, WEBHOOK_SECRET):
    return Response(status=403)

payload = json.loads(body)
if payload["event_type"] == "merchant.approved":
    activate_merchant(payload["merchant_id"])
elif payload["event_type"] == "merchant.rejected":
    flag_merchant_for_review(payload["merchant_id"], payload.get("rejection_reason"))

Node.js:

javascript
const crypto = require("crypto");

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

// In your Express handler:
app.post("/webhooks/pb", express.raw({ type: "*/*" }), (req, res) => {
  const sig = req.headers["x-paybridge-signature"];
  if (!verifyWebhook(req.body, sig, WEBHOOK_SECRET)) {
    return res.sendStatus(403);
  }

  const payload = JSON.parse(req.body);
  if (payload.event_type === "merchant.approved") {
    activateMerchant(payload.merchant_id);
  }
  res.sendStatus(200);
});

Important: Return HTTP 200 promptly. If your endpoint doesn't respond within 10 seconds, PayBridge will retry delivery.


Step 5: Check Onboarding Status (Polling) ​

You can also poll for status instead of (or in addition to) relying on webhooks. The status endpoint actively checks with the processor for updates when the status is still pending.

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

Response 200 OK:

json
{
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000",
  "processors": [
    {
      "processor_id": "660e8400-e29b-41d4-a716-446655440001",
      "type": "clearent",
      "onboarding_status": "approved",
      "processor_merchant_id": "100209766",
      "submitted_at": "2026-03-25T10:01:00Z",
      "approved_at": "2026-03-27T14:30:00Z",
      "rejection_reason": null
    }
  ]
}

Onboarding Status Values ​

StatusMeaningAction
pendingApplication created, waiting for submissionMerchant needs to complete the hosted form
submittedApplication submitted to processorWait for processor review
pending_reviewUnder review at processorWait (typically 1–3 business days)
approvedMerchant account approvedMerchant can now accept payments
rejectedApplication deniedCheck rejection_reason, re-submit if correctable
draftA programmatic boarding step failed partway (the processor application exists but is incomplete)Recoverable — re-submit POST /merchants/{id}/onboard to resume from where it stopped

Step 6: Test Your Webhook Handler ​

Before going live, verify your webhook endpoint works:

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

/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"
}

If delivered is false, check the error field and ensure your endpoint is reachable from the internet.


Error Handling ​

Common Errors ​

StatusEndpointCauseResolution
401AllMissing or invalid X-API-KeyCheck your API key
404/merchants/{id}/*Merchant not found (or belongs to a different app)Verify the merchant ID
409/merchants/{id}/onboard/hostedMerchant already has a processor in a non-retriable statusCheck existing processor status first
400/merchants/{id}/onboard/hostedHosted onboarding creation failedCheck the detail field for specifics
502/merchants/{id}/onboard/hostedProcessor communication errorSafe to retry

Missing the CSRF Header ​

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

X-Requested-With: PayBridge

If you forget this header, you'll get a 403 Forbidden response. Server-to-server webhook endpoints (/webhooks/*) and admin Bearer-authenticated endpoints are exempt.


Rate Limits ​

EndpointLimit
POST /merchants20 requests/min
POST /merchants/{id}/onboard/hosted20 requests/min
GET /merchants/{id}/onboard/status100 requests/min
POST /webhooks/test200 requests/min

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


What's Next? ​

Once a merchant is approved, they can accept payments. See:

  • Getting Started — full integration guide including checkout and refunds
  • API Reference — complete endpoint documentation
  • Webhooks — full webhook reference with all event types and retry behavior