What are customers?

A customer is a program-scoped wrapper that links an entity to a specific OpenFX program. While entities hold identity information (legal name, address, documents), customers hold program context: KYB status, compliance capabilities, and the business relationship with OpenFX. This separation is intentional:
  • Entities answer: Who is this person or business?
  • Customers answer: What can they do on this program?
In the MVP API, customers are created through orchestrated onboarding. The onboarding endpoint creates both the entity and the customer in a single call. Use the endpoints below to retrieve and list customers after they have been created.

Why Entity-Customer separation?

The Entity-Customer separation is one of the most important architectural decisions in the platform:
  • Entity = legal and compliance anchor. Holds: legal name, address, tax ID, documents, ownership relationships (beneficial owners, directors, authorized signers).
  • Customer = program-scoped wrapper. Holds: KYB status, capabilities, program enrollment, metadata.
One entity can map to multiple customers — the same legal entity can participate in different OpenFX programs, each with its own customer record, KYB status, and capability set. Identity data (documents, relationships, legal name) is managed once on the entity and shared across all customer enrollments.
Entity fields and customer fields are never conflated. Entities hold identity information. Customers hold program-scoped enrollment and compliance status.

Customer lifecycle

Customers progress through a review-oriented lifecycle before they can transact.
StatusDescription
draftCreated but not yet submitted for review.
submittedSubmitted for KYB review.
under_reviewKYB review is in progress. This is an asynchronous process.
activeApproved and operational. The customer can now create accounts, payments, and conversions.
rejectedKYB review failed. Terminal state.
suspendedTemporarily disabled due to a compliance action. Can be reactivated.
closedPermanently deactivated. Terminal state.
Once a customer reaches rejected or closed status, it cannot be reactivated. If a customer is rejected, you will need to address the issues and create a new onboarding.

KYB status tracking

The kybStatus field on the customer tracks the Know Your Business verification process separately from the customer status. This provides more granular insight into where the review stands.
KYB StatusDescription
not_startedKYB review has not been initiated. Customer is in draft.
pendingKYB review is in progress. Waiting for a decision.
approvedKYB review passed. Customer is eligible for active status.
rejectedKYB review failed. Customer transitions to rejected.
requires_infoAdditional information or documents are needed.
KYB requirement: A customer must reach kybStatus: approved before deposit instructions are shown and the customer can begin transacting. This gate ensures compliance review is completed before any money movement is possible.
When KYB status changes to requires_info, OpenFX needs more documentation or data from you. Monitor the customer.kyb_status_changed webhook for updates.

Progressive capabilities

Capabilities are granted to customers progressively as their compliance profile matures. The capabilities array on the customer resource tells you exactly what the customer is allowed to do.
CapabilityWhat it unlocks
accountsCreate and manage fiat and crypto accounts
paymentsCreate outbound payments across all rails
fxAccess the FX engine for quotes and conversions
cryptoUse crypto-specific rails and blockchain addresses
Capabilities are read-only — they are granted by the system based on the customer’s compliance status and cannot be set or modified via the API.
{
  "id": "cus_01953e1a5f4b7000",
  "entityId": "ent_01953e1a5f4b7200",
  "type": "business",
  "status": "active",
  "kybStatus": "approved",
  "capabilities": ["accounts", "payments", "fx", "crypto"],
  ...
}
Check the capabilities array before attempting operations. For example, if "payments" is not in the capabilities list, calls to POST /payments will fail for this customer. This lets you build UIs that proactively disable features the customer is not yet approved for.

Handling KYB webhooks

KYB review is asynchronous. Use webhooks to react to status changes rather than polling.

Relevant webhook events

EventWhen it fires
customer.status_changedCustomer status transitions (e.g., submitted to active, or under_review to rejected)
customer.kyb_status_changedKYB status transitions (e.g., pending to approved, or pending to requires_info)

Example webhook payload

{
  "id": "evt_01953e1a5f4b7009",
  "type": "customer.kyb_status_changed",
  "createdAt": "2026-02-23T14:30:00Z",
  "data": {
    "resourceType": "customer",
    "resourceId": "cus_01953e1a5f4b7000",
    "snapshot": {
      "id": "cus_01953e1a5f4b7000",
      "entityId": "ent_01953e1a5f4b7200",
      "type": "business",
      "status": "active",
      "kybStatus": "approved",
      "capabilities": ["accounts", "payments", "fx", "crypto"]
    },
    "previousAttributes": {
      "status": "under_review",
      "kybStatus": "pending",
      "capabilities": []
    }
  }
}
The previousAttributes field on status change events lets you determine exactly what changed without needing to track the previous state yourself. Compare snapshot (current) with previousAttributes (previous) to identify the transition.

Handling different outcomes

def handle_kyb_webhook(event):
    """Handle customer.kyb_status_changed webhook events."""
    customer = event["data"]["snapshot"]
    kyb_status = customer["kybStatus"]

    if kyb_status == "approved":
        # Customer is now active -- create accounts, enable features
        print(f"Customer {customer['id']} approved!")
        print(f"Capabilities: {customer['capabilities']}")
        create_default_accounts(customer["id"])

    elif kyb_status == "rejected":
        # KYB failed -- notify your team, do not retry automatically
        print(f"Customer {customer['id']} rejected")
        notify_compliance_team(customer["id"])

    elif kyb_status == "requires_info":
        # Additional documentation needed -- prompt the user
        print(f"Customer {customer['id']} needs more info")
        request_additional_documents(customer["entityId"])

Listing customers

# List all customers
curl -X GET "https://sandbox.api.openfx.com/v1/customers?limit=20" \
  -H "Authorization: Bearer ofx_sk_sandbox_abc123def456" \
  -H "X-Signature: <your-ed25519-signature>" \
  -H "X-Timestamp: 1740500000"

# Filter by status
curl -X GET "https://sandbox.api.openfx.com/v1/customers?status=active&limit=50" \
  -H "Authorization: Bearer ofx_sk_sandbox_abc123def456" \
  -H "X-Signature: <your-ed25519-signature>" \
  -H "X-Timestamp: 1740500000"

Next steps

Once your customer reaches active status, you can begin transacting:

API reference