Every customer you onboard is a direct customer of MoonPay Enterprise for compliance purposes, whichever onboarding model you use (hosted, shared, or reliance). MoonPay Enterprise stays responsible for compliance oversight: onboarding, ongoing monitoring, and transaction review. You give your customers access to the services but don’t act as the service provider. Your company can also use MoonPay Enterprise for its own operations while onboarding customers.
Steps to Onboard and Activate a Customer
1
Create a new customer
POST /api/customersCreate a customer record. Present the terms and conditions next, before you create an identification.The status the customer is created in depends on the API version you send:The version is pinned to the customer when you create it, and it drives that customer’s onboarding for its lifetime. Existing customers keep the behavior they were created with, so adopting
2026-08-01 only affects customers you create after you start sending it. See API Versioning.API Example
API Example
2
Present and sign the terms and conditions
GET /api/terms-and-conditions?country={ISO3}Fetch and present the terms, then record acceptance via POST /api/customers/{id}/signings. The terms contain the data sharing agreement, so your customer must accept them before MoonPay Enterprise collects KYC data. country is a required parameter, but as of the terms update in the changelog it no longer changes which document you get back. See Terms and Conditions for the current behavior and what changed.API Example
API Example
url to your customer for review. The signing request takes content_id (the id from the terms response) and signed: true.3
Verify the customer's identity
POST /api/customers/{id}/identifications/v2Create an identification using one of these methods:- Hosted MoonPay Enterprise KYC link
- SumSub token sharing
- KYC/KYB Sharing
API Example
API Example
4
KYC is approved and the customer is activated
Once approved, MoonPay Enterprise checks whether the customer has signed the current terms. If they have, and nothing else is outstanding, the customer becomes
Example response from required-signings:Pass the
Active and you can move on. Having signed the terms in step 2, that is the normal outcome.Handle the other branch anyway. If anything is outstanding the status is SigningsRequired instead: call GET /api/customers/{id}/required-signings, present each returned document, and mark each as signed via POST /api/customers/{id}/signings. The customer becomes Active once none are left.When nothing is outstanding, the response depends on your API version: 2026-08-01 and later return 200 with an empty list, earlier versions return 409 Conflict.This is not only an onboarding branch. An
Active customer returns to SigningsRequired whenever a new document is published, and cannot transact until they sign it, so the flow you build here needs to stay reachable for the life of the account. See Signing new terms later.API Example
API Example
url to your customer for review. The signing request takes content_id (the id from required-signings) and signed: true.5
Check payout rail availability
abilities.fiat_payout is nested by currency, then by rail. Check the specific rail you plan to use, for example abilities.fiat_payout.usd.ach === "Active", before initiating payouts._thirdparty variant (e.g. ach_thirdparty) for payouts to a third party. The abilities object also returns fiat_deposit with the same shape and a currencies array listing the flows (mint, redeem, onramp, offramp, swap) available per currency.Call the endpoint once here, then subscribe to the customer_fiat_abilities webhook and keep your copy current from the deliveries. Rails turn on at times you cannot predict from your own requests, so the webhook replaces polling this endpoint. See Tracking ability changes.Before an active customer can transact, register their wallet addresses for Travel Rule compliance. Self-hosted wallets register with a signed proof-of-ownership message, or, for US and Rest of World customers, by self-attestation. See the Crypto Addresses guide.
A customer’s status reverts from
Active to SigningsRequired or IdentificationRequired when new compliance actions are required (e.g. updated terms and conditions, fraud review, enhanced due diligence). Plan for this: see Signing new terms later.Terms and Conditions
The terms and conditions contain the data sharing agreement between MoonPay Enterprise and your customer. Your customer must accept them before MoonPay Enterprise collects KYC data, so present the terms right after creating the customer and before creating an identification.Fetching Terms Before KYC
For a customer created on2026-08-01 or later, GET /api/customers/{id}/required-signings returns the outstanding document at any point in their lifecycle, including before identification. Prefer it: no country parameter, and the same call serves onboarding and every document afterwards.
The endpoint below is still the way to fetch the terms before a customer record exists, and the only way for customers created on an earlier version.
GET /api/terms-and-conditions?country={ISO3}
MoonPay Enterprise serves a single global document, the same for every customer regardless of country.
country is still a required parameter, but its value no longer changes which document you get back. Pass the customer’s ISO 3166-1 alpha-3 code. A 3-letter code MoonPay Enterprise doesn’t map to a region (for example MCO) returns 500, so don’t retry it. The response is a list with id, url, and display_name, the same shape as required-signings. Present the url to your customer, then record acceptance via POST /api/customers/{id}/signings with content_id and signed: true.
A malformed country code returns 400 with a plain string body:
This is the current behavior, in effect since the terms update noted in the changelog. Earlier API versions still resolve a region-specific document from
country, and compare the signed terms against the region KYC verifies. See the changelog entry for that behavior if you’re integrating against an earlier version.Validation After KYC
After the identification is approved, MoonPay Enterprise checks that the customer has signed the current terms document. If they have, and nothing else is outstanding, they’reActive; otherwise GET /api/customers/{id}/required-signings returns the document to present and sign.
Signing New Terms Later
Signing terms is not a one-time onboarding step. When MoonPay Enterprise publishes a new document, the previous signing no longer satisfies the requirement: affected customers move fromActive back to SigningsRequired, and GET /api/customers/{id}/required-signings returns the new document. They cannot transact until they sign it.
This is how new capabilities reach your customers. Access to an additional currency, a new payment rail, or a new product often depends on terms your customer has not seen yet. The faster they can sign, the faster you can turn those features on for them.
So treat the signing flow you build during onboarding as a permanent, reusable path rather than a step you run once:
- Poll or subscribe. Subscribe to the
customer_statuswebhook and treat a transition intoSigningsRequiredas a prompt to act, rather than discovering it from a failed transaction. - Reuse one code path. For customers created on
2026-08-01or later,required-signingsreturns whatever is currently outstanding at any point in the lifecycle. The same function that handles onboarding handles every later document. - Never cache “terms signed” as a permanent flag. Match on the
content_idthatrequired-signingsreturns. A customer who signed last year’s document has a signing on file but still owes the current one. - Keep it reachable in your UI. Your customer should be able to sign from wherever they already are, without contacting your support team or restarting onboarding.
Signings are kept for audit, never replaced.
GET /api/customers/{id}/signings returns the full history, so an earlier document and the current one both appear. Presence of a signing does not mean the current document is signed. Compare against required-signings.Customer email branding
Customer emails can use your company’s logo, sender name, and email domain. Contact the MoonPay Enterprise team to enable this option. We configure the sender and provide DNS records for you to publish to verify your domain and authorize email sending. An optional custom MAIL FROM subdomain also lets emails use your domain for bounce handling and SPF alignment. Dashboard-related emails continue to come from MoonPay Enterprise.Handling Missing Information
If an identification is incomplete, the customer’s status isIdentificationRequired and a url is returned on the Identification object. Redirect your customer to this URL. It opens a hosted step-up flow that collects only the missing data.
This occurs when:
- A data point is found to be invalid, expired, or inconsistent
- A limit triggers additional due diligence requirements
400 at creation, in every country, and no identification is created. See Incomplete Submissions.
Checking Outstanding Requirements
GET /api/customers/{id}/requirements
The customer status tells you the stage of onboarding. This endpoint tells you which verification requirements are satisfied, awaiting review, or need attention. Use X-API-Version: 2026-10-01 or later for the response described here. See the API Reference for the selected version’s schema.
array
required
Verification categories with outstanding requirements:
kyc, kyb, or an empty array. Includes requirements awaiting review, so a non-empty array does not always mean the customer needs to act. An empty array does not confirm that terms are signed or payment rails are active.array
required
Requirements the customer has satisfied. Each item has
code and description.array
required
Requirements awaiting review. Each item has
code and description. Show a waiting state for these items.array
required
Requirements awaiting customer input. Each item has
code and description. Questionnaire items also include missing_questions, with each unanswered question’s id, title, and nullable section_id.array
required
Requirements with review issues. Each item includes
code, description, nullable review_comment and retryable, reject_reasons, and rejected document steps. See Handling Review Issues.string | null
required
The identification associated with the response. This is a top-level field, not a field on each requirement. It is
null when no identification is associated with the response.string | null
required
Feedback about the overall submission, when available for requirements awaiting review or with issues. Show it alongside the more specific requirement and document comments. See Displaying Onboarding Comments to Your Customer.
object | null
required
Preliminary assessment, when available. Contains
status, missingInputs, reasons, and workflowErrors. Its status is likely_pass, fail, likely_fail, or inconclusive. This is an early signal, not a verification approval or a replacement for the requirement buckets.pendingReview uses that exact casing.
Earlier API versions and requests without
X-API-Version keep the legacy format: missing means awaiting input, and pending means awaiting review. From 2026-10-01, use pending for input and pendingReview for review, and read reject_reasons[].error_code instead of reject_labels. See API Versions.Requirement Items
Use each item’scode to identify the requirement. Verification codes are kyc_selfie, kyc_identity, kyc_applicant_data, kyc_proof_of_residence, kyc_questionnaire, kyb_company_data, kyb_company_beneficiaries, kyb_company_documents, kyb_questionnaire, identification_kyc, and identification_kyb.
description explains what the requirement is. It is not the reviewer’s feedback or a stable value to match on. Read the comment and rejection fields for what needs fixing. Only pending questionnaire items contain missing_questions; ordinary pending items and completed items do not carry that field.
Handling Review Issues
Review problems are returned inside a successful200 response. Read each issues item even when the HTTP request succeeds. These are separate from the validation errors returned when an identification submission fails with 400.
string | null
required
Customer-facing feedback for this requirement. A
null value does not mean there is no issue: also read general_review_comment, the rejection reasons, and document-step comments.boolean | null
required
true means the customer can correct and resubmit to clear the issue. false means the issue is terminal. null means retryability is unknown; do not treat it as permission to retry. Follow the correction instructions for your integration, and contact support if the next action is unclear.array
required
Review rejection reasons for the requirement.
array
required
Rejected document steps for this requirement. Several steps can have the same document type; keep each step’s
id to distinguish them.verification_document_expired means the document is outside its accepted validity period. verification_screenshots means an original image is required instead of a screenshot. Check both issues[].reject_reasons[].error_code and issues[].steps[].reject_reasons[].error_code, and use the full rejection-code list for their meanings.
Handle verification_unknown_rejection and codes your app does not recognize with a general feedback message. An empty reject_reasons array does not mean approval: screening, fraud, and policy reasons are not exposed by this endpoint. Keep the issue visible and use the available comments and retryable value to guide the next action.
Reading the Buckets Correctly
Read all four buckets together. For example, company data can becomplete, beneficiaries can be in pendingReview, the questionnaire can be in pending, and company documents can be in issues at the same time. Show the questionnaire and document corrections alongside the review progress; do not hide actionable work behind a waiting screen.
complete: No action is needed for that requirement.pendingReview: Show that the item is awaiting review. Do not ask the customer to upload it again just because review is unfinished.pending: Guide the customer to supply the requested information. For questionnaires, usemissing_questionsto identify unanswered questions.issues: Read the comments and rejection codes, then checkretryablebefore offering a correction or resubmission.
identification_kyc or identification_kyb can represent the identification as a whole. In pending, it means the identification flow still needs input or has not started. In pendingReview, it means the submission is being processed or reviewed. Use top-level identification_id to find the associated identification. If it is null, there is no associated identification to resume; start the initial identification after completing required signings.
The following example shows all four buckets for a business customer. Beneficiaries are awaiting review, while the customer still needs to answer questionnaire questions and correct a shareholder registry.
Example 200 response
Example 200 response
404:
Mapping Onboarding Statuses in Your App
Use the customer’sstatus for lifecycle gates, then use the requirements response to decide what verification work to show. Apply the lifecycle gates first: a suspended or archived customer must not see a retry or ready-to-transact screen just because a requirement permits a retry.
For customers in
IdentificationRequired, combine the buckets rather than picking one screen from a single identification status:
The customer object’s
identification_status is deprecated and returns null. When you need the identification’s own status or hosted url, use GET /api/identifications/{id} with the requirements response’s identification_id. If that ID is null, no identification is associated with the response; do not interpret this as an approval or rejection.Pending means the flow is in progress, Processed and PendingReview mean the submission is being processed or reviewed, and Approved means verification is approved. For Declined, check the requirement issues and retryability before offering another attempt. For Expired, follow the restart flow for your integration. Archived is terminal. Use requirement buckets for the individual tasks within that lifecycle.
Customers can return to SigningsRequired when new terms are due. For customers created with an API version earlier than 2026-08-01, present terms before collecting identification data even if their initial status is IdentificationRequired. See Terms and Conditions.
Subscribe to the identification_status webhook to track identification changes and re-fetch requirements. Track rail availability with the customer_fiat_abilities webhook. Active on the customer does not guarantee that a particular payment rail is active.
Tracking EDD Status
Each identification includes awith_edd field that indicates whether Enhanced Due Diligence was applied. This field is an optional boolean:
true: EDD was triggered (either by the partner or automatically by MoonPay Enterprise’s AML checks)false: EDD was explicitly not requirednull: Identification was created before this feature was available
with_edd can be set in two ways:
- Partner-initiated. Pass
with_edd: true(Link flow) or includeedd_questionnaire(Token/Person flow) when creating the identification. See Proactively Increasing Customer Limits. - Automatically by MoonPay Enterprise. If AML checks determine EDD is required (e.g. customer resides in a high-risk jurisdiction), MoonPay Enterprise sets
with_eddtotrueserver-side.
status and with_edd together to understand where a customer is in the verification process:
Edge Cases
Displaying Onboarding Comments to Your Customer
Use the requirements response to tell your customer what needs attention in KYC or KYB. Read feedback at all three levels:- Overall submission:
general_review_comment. - Requirement:
issues[].review_comment. - Document:
issues[].steps[].review_comment, kept with the document’sid,name, anddescription.
null comment does not clear the issue: check the rejection reasons and the other comment levels.
Keep document instructions beside the document they refer to. Deduplicate repeated text within the same context, but keep comments for different documents separate even when the wording matches. An issue comment that repeats general_review_comment is returned as null; document comments can still repeat other feedback.
The example response above contains an overall comment, a company-document comment, and instructions for the rejected shareholder registry. This helper extracts all three levels and retains the requirement and document identifiers for your UI:
reject_reasons[].error_code; see Verification rejection codes for their meanings. Check the issue’s retryable value separately before showing a correction action.
API Endpoints
Idempotency-Key is required on every POST above (POST /api/customers, POST /api/customers/{id}/identifications/v2, POST /api/customers/{id}/signings). Generate a fresh UUID for each new operation, and reuse that same key when retrying that exact request. A retry sent with a new UUID is treated as a new operation and creates a second record. MoonPay Enterprise stores successful responses only, so retrying after an error runs the request again. See Idempotency.Status Reference
Customer Status
Returned on the customer object.Identification Status
Returned on each identification object.
Typical Flow:
Pending → Processed → PendingReview → Approved / Declined
The identification object also includes with_edd (boolean, nullable) to indicate whether EDD was applied. See Tracking EDD Status for the full interpretation table.
Ability Status
Returned on each rail leaf underabilities.fiat_payout and abilities.fiat_deposit (e.g. abilities.fiat_payout.usd.ach).
Rails available per currency:
usd carries ach, wire, rtp, fednow, and swift. eur carries sepa and swift. gbp carries fps, chaps, and swift. brl carries pix. Each has a _thirdparty variant.
Tracking Ability Changes
Abilities move without any request from you. An identification gets approved, your customer signs a new document, compliance changes their review outcome, or a banking provider finishes registering a rail: MoonPay Enterprise recomputes the customer’s abilities and delivers the new snapshot to thecustomer_fiat_abilities webhook.
Subscribe to it and treat the deliveries as the source of truth for which rails are open. GET /api/customers/{id}/abilities is for the first read and for the currencies array, which the webhook does not carry. Polling it on a timer spends rate limit on unchanged responses and still leaves a window where you reject a transaction on a rail that already went Active.
Each delivery is a full snapshot of
fiat_deposit and fiat_payout, not a diff, and MoonPay Enterprise does not suppress a snapshot identical to the last one it sent. Diff against your stored copy so a repeat delivery doesn’t re-trigger whatever you do when a rail opens.