For the complete documentation index, see llms.txt. This page is also available as Markdown.

Business Checkout Integration

How to integrate the business user OTP-based checkout flow — used when a business is redirected to the hosted checkout and the business user needs to authenticate before transacting.

Who this is for

The on-off-ramp checkout supports three auth modes on the same endpoints, via HybridCheckoutHTTPAuthGuard:

Auth
Use case

Merchant API key

Server-to-server integration

Business user JWT

Business is redirected to hosted checkout, the business user authenticates here ← this guide

Customer JWT

End-customer is redirected to hosted checkout, the customer authenticates here

This guide covers the business user JWT path. The endpoints below also accept the other two modes — pick whichever fits your integration.


Step 1 — Send OTP

POST /api/v1/auth/business-twofactor
Content-Type: application/json

{ "email": "owner@acme.com" }
  • Response: 200 { "success": true, "message": "Business two-factor code sent" }

  • A 6-digit numeric OTP is emailed to the user. Token expiry: 2 minutes.

  • Throttling: 5 requests/min per IP. Unknown emails return 200 (no enumeration).

Step 2 — Resend OTP (if needed)

  • Same response shape.

  • Throttling: 5 requests/min per IP and 30s cooldown per user. Hitting the per-user cooldown returns 429 with { "message": "Please wait before requesting another code" }.

  • The token's expiresAt is refreshed to now + 2min on every send.

Step 3 — Verify OTP and receive JWT

Response:

Store data.token.accessToken — you'll send it as Authorization: Bearer <accessToken> on every subsequent call.

Step 4 — Call checkout endpoints

All routes below accept Authorization: Bearer <businessUserAccessToken>.

On-ramp (full session, including merchant-flow features)

Off-ramp

Public read endpoints (no auth required)

Useful at the start of the checkout to populate UI before the user has authed:

Hybrid-auth read endpoints (API key OR business JWT)

These require authentication but accept either auth mode via HybridHTTPAuthGuard:


End-to-end example (curl)


Behavior notes

  • The business user can transact on behalf of any of their customers. Body fields like email are trusted (the business user is identified server-side via the JWT, but the customer they're transacting for is identified by the body). This matches the API-key behavior.

  • Customer JWT auth differs: under a customer JWT, the body's email is overridden to the authenticated customer's email — they can only transact as themselves. Business user JWT does not have this restriction.

  • refresh/:reference scoping: under business user JWT, can refresh any of the merchant's onramps. Under customer JWT, scoped to that customer's records only.

  • Orphaned users (no activeBusinessId) will get a 401 Merchant context unavailable from any checkout route. The verify endpoint will still succeed and issue a JWT — only the downstream calls fail.

Errors you'll see

HTTP
When

200 (silent) on /business-twofactor

Unknown email — by design, no enumeration

400 Invalid token on /verify

OTP wrong or already expired (2 min window)

429 Please wait before requesting another code

Resend hit the 30s per-user cooldown

429 ThrottlerException

Hit the 5/min IP throttle on send/resend

401 Merchant context unavailable

JWT valid but user has no active business

409 DUPLICATE_TRANSACTION

reference already used — generate a new UUID

404 CUSTOMER_NOT_FOUND

email in body doesn't match a customer; use the WithCustomer variant to create inline

Endpoints that remain merchant-only (API key required)

These don't accept JWTs — they're internal/operational:

  • POST /api/v1/onramp/test, POST /api/v1/offramp/webhook/simulate (webhook simulators)

  • POST /api/v1/offramp/crypto-sent (callback hook)

  • GET /api/v1/onramp/minimum-amount/:currency, GET /api/v1/offramp/minimum-amount/:token

  • POST /api/v1/exchange-rates/onramp, POST /api/v1/exchange-rates/offramp

Last updated